Skip to content

Commit d894950

Browse files
committed
Implemented json saves among binary save file convertion
1 parent 28fd730 commit d894950

6 files changed

Lines changed: 299 additions & 24 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,6 @@ modding/
9696
node_modules
9797
/src/port/build.c
9898
/properties.h
99+
!/src/port/mods/
100+
mods/
101+
saves/

src/game/save_file.c

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
#include "level_table.h"
1212
#include "course_table.h"
1313
#include "rumble_init.h"
14+
#include "port/data/Saves.h"
1415

1516
#define MENU_DATA_MAGIC 0x4849
1617
#define SAVE_FILE_MAGIC 0x4441
1718

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

2022
extern struct SaveBuffer gSaveBuffer;
2123

@@ -148,6 +150,8 @@ static void add_save_block_signature(void *buffer, s32 size, u16 magic) {
148150
* Copy main menu data from one backup slot to the other slot.
149151
*/
150152
static void restore_main_menu_data(s32 srcSlot) {
153+
CALL(RestoreMainMenuData, srcSlot);
154+
151155
s32 destSlot = srcSlot ^ 1;
152156

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

163167
static void save_main_menu_data(void) {
168+
CALL(SaveMainMenuData);
169+
164170
if (gMainMenuDataModified) {
165171
// Compute checksum
166172
add_save_block_signature(&gSaveBuffer.menuData[0], sizeof(gSaveBuffer.menuData[0]), MENU_DATA_MAGIC);
@@ -234,6 +240,8 @@ static void touch_high_score_ages(s32 fileIndex) {
234240
* Copy save file data from one backup slot to the other slot.
235241
*/
236242
static void restore_save_file_data(s32 fileIndex, s32 srcSlot) {
243+
CALL(RestoreSaveFileData, fileIndex, srcSlot);
244+
237245
s32 destSlot = srcSlot ^ 1;
238246

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

252260
void save_file_do_save(s32 fileIndex) {
261+
CALL(SaveFileDoSave, fileIndex);
262+
253263
if (gSaveFileModified) {
254264
// Compute checksum
255265
add_save_block_signature(&gSaveBuffer.files[fileIndex][0],
@@ -296,40 +306,45 @@ void save_file_load_all(void) {
296306
gSaveFileModified = FALSE;
297307

298308
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-
}
315309

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;
310+
if(ShouldLoadOldSaveFile()) {
311+
read_eeprom_data(&gSaveBuffer, EEPROM_SIZE);
312+
313+
// Verify the main menu data and create a backup copy if only one of the slots is valid.
314+
validSlots = verify_save_block_signature(&gSaveBuffer.menuData[0], MENU_SAVE_DATA_SIZE, MENU_DATA_MAGIC);
315+
validSlots |= verify_save_block_signature(&gSaveBuffer.menuData[1], MENU_SAVE_DATA_SIZE, MENU_DATA_MAGIC) << 1;
320316
switch (validSlots) {
321317
case 0: // Neither copy is correct
322-
save_file_erase(file);
318+
wipe_main_menu_data();
323319
break;
324320
case 1: // Slot 0 is correct and slot 1 is incorrect
325-
restore_save_file_data(file, 0);
321+
restore_main_menu_data(0);
326322
break;
327323
case 2: // Slot 1 is correct and slot 0 is incorrect
328-
restore_save_file_data(file, 1);
324+
restore_main_menu_data(1);
329325
break;
330326
}
327+
328+
for (file = 0; file < NUM_SAVE_FILES; file++) {
329+
// Verify the save file and create a backup copy if only one of the slots is valid.
330+
validSlots = verify_save_block_signature(&gSaveBuffer.files[file][0], SAVE_FILE_SIZE, SAVE_FILE_MAGIC);
331+
validSlots |= verify_save_block_signature(&gSaveBuffer.files[file][1], SAVE_FILE_SIZE, SAVE_FILE_MAGIC) << 1;
332+
switch (validSlots) {
333+
case 0: // Neither copy is correct
334+
save_file_erase(file);
335+
break;
336+
case 1: // Slot 0 is correct and slot 1 is incorrect
337+
restore_save_file_data(file, 0);
338+
break;
339+
case 2: // Slot 1 is correct and slot 0 is incorrect
340+
restore_save_file_data(file, 1);
341+
break;
342+
}
343+
}
331344
}
332345

346+
CALL(SaveFileLoadAll);
347+
333348
stub_save_file_1();
334349
}
335350

src/game/save_file.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "course_table.h"
1010

1111
#define EEPROM_SIZE 0x200
12+
#define SAVE_FILE_SIZE 0x38
13+
#define MENU_SAVE_DATA_SIZE 0x20
1214
#define NUM_SAVE_FILES 4
1315

1416
struct SaveBlockSignature {
@@ -34,6 +36,8 @@ struct SaveFile {
3436
u8 courseCoinScores[COURSE_STAGES_COUNT];
3537

3638
struct SaveBlockSignature signature;
39+
40+
// @port: Custom data needs to go after this point
3741
};
3842

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

6367
struct SaveBlockSignature signature;
68+
69+
// @port: Custom data needs to go after this point
6470
};
6571

6672
struct SaveBuffer {
@@ -120,6 +126,10 @@ struct WarpCheckpoint {
120126
/*0x04*/ u8 warpNode;
121127
};
122128

129+
#ifdef __cplusplus
130+
extern "C" {
131+
#endif
132+
123133
extern struct WarpCheckpoint gWarpCheckpoint;
124134

125135
extern s8 gMainMenuDataModified;
@@ -164,4 +174,8 @@ void eu_set_language(u16 language);
164174
u16 eu_get_language(void);
165175
#endif
166176

177+
#ifdef __cplusplus
178+
}
179+
#endif
180+
167181
#endif // SAVE_FILE_H

src/port/data/SaveConversion.h

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#pragma once
2+
3+
#include "game/save_file.h"
4+
5+
#include <nlohmann/json.hpp>
6+
7+
using json = nlohmann::json;
8+
9+
#define SAVE_FILE_VERSION 1
10+
11+
static std::string entries[] = {
12+
"CG", "BOB", "WF", "JRB", "CCM", "BBH", "HMC", "LLL", "SSL", "DDD", "SL",
13+
"WDW", "TTM", "THI", "TTC", "RR", "BITDW", "BITFS", "BITS", "PSS", "COTMC",
14+
"TOTWC", "VCUTM", "WMOTR", "SA", "CAKE_END"
15+
};
16+
17+
template<typename T>
18+
T GetSafeEntry(const json& node, const std::string& key) {
19+
if(!node.contains(key)) {
20+
auto dump = nlohmann::json(node).dump(4);
21+
throw std::runtime_error("JSON missing the '" + key + "' entry\nProblematic JSON:\n" + dump);
22+
}
23+
24+
return node.at(key).get<T>();
25+
}
26+
27+
template<typename T>
28+
T GetSafeEntry(const json& node, const std::string& key, const T& def) {
29+
if(!node.contains(key)) {
30+
return def;
31+
}
32+
33+
return node.at(key).get<T>();
34+
}
35+
36+
inline void to_json(json& j, const SaveFile& save) {
37+
json stars = {};
38+
json coins = {};
39+
json cap = {
40+
{ "x", save.capPos[0] },
41+
{ "y", save.capPos[1] },
42+
{ "z", save.capPos[2] },
43+
};
44+
for (size_t i = 0; i < COURSE_COUNT; i++) {
45+
stars[entries[i]] = save.courseStars[i];
46+
}
47+
48+
for (size_t i = 0; i < COURSE_STAGES_COUNT; i++) {
49+
coins[entries[i]] = save.courseCoinScores[i];
50+
}
51+
52+
j = json{
53+
{ "version", SAVE_FILE_VERSION },
54+
{ "capLevel", save.capLevel },
55+
{ "capArea", save.capArea },
56+
{ "capPos", cap },
57+
{ "flags", save.flags },
58+
{ "courseStars", stars },
59+
{ "courseCoinScores", coins }
60+
};
61+
}
62+
63+
inline void LoadSaveFileV1(const json& j, SaveFile& save) {
64+
json capPosJson = GetSafeEntry<json>(j, "capPos");
65+
save.capLevel = GetSafeEntry(j, "capLevel", 0);
66+
save.capArea = GetSafeEntry(j, "capArea", 0);
67+
save.capPos[0] = GetSafeEntry(capPosJson, "x", 0);
68+
save.capPos[1] = GetSafeEntry(capPosJson, "y", 0);
69+
save.capPos[2] = GetSafeEntry(capPosJson, "z", 0);
70+
save.flags = GetSafeEntry(j, "flags", 0);
71+
72+
json starsJson = GetSafeEntry<json>(j, "courseStars");
73+
for (size_t i = 0; i < COURSE_COUNT; i++) {
74+
save.courseStars[i] = GetSafeEntry<u8>(starsJson, entries[i]);
75+
}
76+
77+
json coinsJson = GetSafeEntry<json>(j, "courseCoinScores");
78+
for (size_t i = 0; i < COURSE_STAGES_COUNT; i++) {
79+
save.courseCoinScores[i] = GetSafeEntry<u8>(coinsJson, entries[i]);
80+
}
81+
}
82+
83+
inline void from_json(const json& j, SaveFile& save) {
84+
uint32_t version = GetSafeEntry<uint32_t>(j, "version");
85+
if(version == 1) {
86+
LoadSaveFileV1(j, save);
87+
}
88+
}
89+
90+
inline void to_json(json& j, const MainMenuSaveData& menu) {
91+
json coins = json::array();
92+
for (size_t i = 0; i < NUM_SAVE_FILES; i++) {
93+
coins.push_back(menu.coinScoreAges[i]);
94+
}
95+
96+
j = json{
97+
{ "version", SAVE_FILE_VERSION },
98+
{ "coinScoreAges", coins },
99+
{ "soundMode", menu.soundMode }
100+
};
101+
}
102+
103+
inline void LoadMainMenuSaveDataV1(const json& j, MainMenuSaveData& menu) {
104+
json coinsJson = GetSafeEntry<json>(j, "coinScoreAges");
105+
for (size_t i = 0; i < NUM_SAVE_FILES; i++) {
106+
menu.coinScoreAges[i] = coinsJson.at(i).get<u32>();
107+
}
108+
109+
menu.soundMode = GetSafeEntry<u16>(j, "soundMode", 0);
110+
}
111+
112+
inline void from_json(const json& j, MainMenuSaveData& menu) {
113+
uint32_t version = GetSafeEntry<uint32_t>(j, "version");
114+
if(version == 1) {
115+
LoadMainMenuSaveDataV1(j, menu);
116+
}
117+
}

src/port/data/Saves.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#include "Saves.h"
2+
3+
#include "port/ShipInit.hpp"
4+
#include "port/data/SaveConversion.h"
5+
6+
#include <fstream>
7+
#include <filesystem>
8+
namespace fs = std::filesystem;
9+
10+
extern struct SaveBuffer gSaveBuffer;
11+
12+
static void Init() {
13+
// Create saves directory if it doesn't exist
14+
fs::path dir("saves");
15+
if (!fs::exists(dir)) {
16+
fs::create_directory(dir);
17+
}
18+
}
19+
20+
extern "C" {
21+
void RestoreMainMenuData(int32_t srcSlot) {
22+
int32_t destSlot = srcSlot ^ 1;
23+
24+
bcopy(&gSaveBuffer.menuData[srcSlot], &gSaveBuffer.menuData[destSlot], sizeof(gSaveBuffer.menuData[destSlot]));
25+
}
26+
27+
void RestoreSaveFileData(int32_t fileIndex, int32_t srcSlot) {
28+
int32_t destSlot = srcSlot ^ 1;
29+
30+
bcopy(&gSaveBuffer.files[fileIndex][srcSlot], &gSaveBuffer.files[fileIndex][destSlot], sizeof(gSaveBuffer.files[fileIndex][destSlot]));
31+
}
32+
33+
void SaveFileDoSave(int32_t fileIndex) {
34+
std::ofstream file(fs::path("saves/save_" + std::to_string(fileIndex) + ".json"), std::ios::out);
35+
if (!file.is_open()) {
36+
return;
37+
}
38+
39+
json j = gSaveBuffer.files[fileIndex][0];
40+
file << j.dump(4);
41+
file.close();
42+
}
43+
44+
bool ShouldLoadOldSaveFile(void) {
45+
fs::path save("default.sav");
46+
return fs::exists(save);
47+
}
48+
49+
void SaveFileLoadAll(void) {
50+
fs::path save("default.sav");
51+
if (fs::exists(save)) {
52+
for (int32_t fileIndex = 0; fileIndex < NUM_SAVE_FILES; fileIndex++) {
53+
SaveFileDoSave(fileIndex);
54+
}
55+
// Move old save files to backup
56+
fs::rename(save, "default.sav.bak");
57+
return;
58+
}
59+
60+
// Read save files
61+
for (int32_t fileIndex = 0; fileIndex < NUM_SAVE_FILES; fileIndex++) {
62+
fs::path filepath = fs::path("saves/save_" + std::to_string(fileIndex) + ".json");
63+
if (!fs::exists(filepath)) {
64+
continue;
65+
}
66+
67+
std::ifstream file(filepath, std::ios::in);
68+
if (!file.is_open()) {
69+
continue;
70+
}
71+
72+
json j;
73+
file >> j;
74+
gSaveBuffer.files[fileIndex][0] = j.get<struct SaveFile>();
75+
file.close();
76+
}
77+
78+
// Read global save file
79+
fs::path globalpath = fs::path("saves/global.json");
80+
if (fs::exists(globalpath)) {
81+
std::ifstream file(globalpath, std::ios::in);
82+
if (file.is_open()) {
83+
json j;
84+
file >> j;
85+
gSaveBuffer.menuData[0] = j.get<struct MainMenuSaveData>();
86+
file.close();
87+
}
88+
}
89+
}
90+
91+
void SaveMainMenuData(void) {
92+
std::ofstream file(fs::path("saves/global.json"), std::ios::out);
93+
if (!file.is_open()) {
94+
return;
95+
}
96+
97+
json j = gSaveBuffer.menuData[0];
98+
file << j.dump(4);
99+
file.close();
100+
}
101+
102+
}
103+
104+
static RegisterShipInitFunc initFunc(Init);

0 commit comments

Comments
 (0)