Skip to content

Commit 06151d5

Browse files
committed
Trying to fix old saves
1 parent dc015a4 commit 06151d5

2 files changed

Lines changed: 74 additions & 11 deletions

File tree

src/engine/level_script.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ struct LevelCommand {
99
/*02*/ // variable sized argument data
1010
};
1111

12-
extern_s LevelScript level_script_entry[];
12+
extern_s const LevelScript level_script_entry[];
1313
extern_s struct AllocOnlyPool *sLevelPool;
1414

1515
extern_s struct LevelCommand *level_script_execute(struct LevelCommand *cmd);

src/port/data/SaveConversion.h

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "port/mods/achievements/Achievements.h"
88

99
#include <nlohmann/json.hpp>
10+
#include <cstring>
1011

1112
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ShipSaveFeatures, achievements, rando)
1213
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(RandoSaveCheck, randoItemId, randoAct, obtained, skipped)
@@ -15,7 +16,9 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(RandoSaveData, randoSaveChecks, randoSaveEntr
1516

1617
using json = nlohmann::json;
1718

18-
#define SAVE_FILE_VERSION 1
19+
#define SAVE_FILE_VERSION 2
20+
#define SAVE_FILE_VERSION_LEGACY 1
21+
#define SAVE_FILE_VERSION_ACHIEVEMENT_MIGRATION 2
1922

2023
static std::string entries[] = {
2124
"BOB", "WF", "JRB", "CCM", "BBH", "HMC", "LLL", "SSL", "DDD", "SL",
@@ -42,6 +45,24 @@ T GetSafeEntry(const json& node, const std::string& key, const T& def) {
4245
return node.at(key).get<T>();
4346
}
4447

48+
// Reconstruct achievement data from legacy save file state
49+
inline void ReconstructAchievementData(const SaveFile& save, AchievementSaveData& achievementData) {
50+
achievementData.cheated = false;
51+
achievementData.capStars = 0;
52+
53+
// Infer coins from highest coin score across all courses
54+
int32_t maxCoins = 0;
55+
for (size_t i = 0; i < COURSE_STAGES_COUNT; i++) {
56+
if (save.courseCoinScores[i] > maxCoins) {
57+
maxCoins = save.courseCoinScores[i];
58+
}
59+
}
60+
achievementData.coins = maxCoins;
61+
62+
// Note: capStars cannot be reliably reconstructed without knowing HOW each star was obtained
63+
// (metal cap vs no metal cap), so we leave it at 0 and let players re-earn if needed
64+
}
65+
4566
inline void from_json(const json& j, AchievementSaveEntry& entry) {
4667
const auto id = GetSafeEntry<std::string>(j, "id");
4768
const auto progress = GetSafeEntry<int32_t>(j, "progress");
@@ -61,12 +82,14 @@ inline void to_json(json& j, const AchievementSaveEntry& entry) {
6182

6283
inline void from_json(const json& j, AchievementSaveData& data) {
6384
data.cheated = j.at("cheated").get<bool>();
64-
data.capStars = j.at("capStars").get<int>();
65-
data.coins = j.at("coins").get<int>();
66-
67-
auto entriesJson = j.at("entries");
68-
for (size_t i = 0; i < entriesJson.size(); i++) {
69-
data.entries[i] = entriesJson.at(i).get<AchievementSaveEntry>();
85+
data.capStars = GetSafeEntry(j, "capStars", 0);
86+
data.coins = GetSafeEntry(j, "coins", 0);
87+
88+
if (j.contains("entries")) {
89+
auto entriesJson = j.at("entries");
90+
for (size_t i = 0; i < entriesJson.size(); i++) {
91+
data.entries[i] = entriesJson.at(i).get<AchievementSaveEntry>();
92+
}
7093
}
7194
}
7295

@@ -91,8 +114,13 @@ inline void from_json(const json& j, ShipSaveData& save) {
91114
j["randoSaveData"].get_to(save.randoSaveData);
92115
}
93116

94-
if (save.features.achievements) {
117+
if (j.contains("achievementSaveData")) {
95118
j["achievementSaveData"].get_to(save.achievementSaveData);
119+
} else if (save.features.achievements) {
120+
memset(&save.achievementSaveData, 0, sizeof(AchievementSaveData));
121+
save.achievementSaveData.cheated = false;
122+
save.achievementSaveData.capStars = 0;
123+
save.achievementSaveData.coins = 0;
96124
}
97125
}
98126

@@ -137,7 +165,39 @@ inline void to_json(json& j, const SaveFile& save) {
137165
};
138166
}
139167

140-
inline void LoadSaveFileV1(const json& j, SaveFile& save) {
168+
inline void LoadSaveFileLegacy(const json& j, SaveFile& save) {
169+
json capPosJson = GetSafeEntry<json>(j, "capPos", json::object());
170+
save.capLevel = GetSafeEntry(j, "capLevel", 0);
171+
save.capArea = GetSafeEntry(j, "capArea", 0);
172+
save.capPos[0] = GetSafeEntry(capPosJson, "x", 0);
173+
save.capPos[1] = GetSafeEntry(capPosJson, "y", 0);
174+
save.capPos[2] = GetSafeEntry(capPosJson, "z", 0);
175+
save.flags = GetSafeEntry(j, "flags", 0);
176+
177+
json starsJson = GetSafeEntry<json>(j, "courseStars", json::object());
178+
for (size_t i = 0; i < COURSE_COUNT; i++) {
179+
save.courseStars[i] = GetSafeEntry<u8>(starsJson, entries[i], static_cast<u8>(0));
180+
}
181+
182+
json coinsJson = GetSafeEntry<json>(j, "courseCoinScores", json::object());
183+
for (size_t i = 0; i < COURSE_STAGES_COUNT; i++) {
184+
save.courseCoinScores[i] = GetSafeEntry<u8>(coinsJson, entries[i], static_cast<u8>(0));
185+
}
186+
187+
memset(&save.shipSaveData, 0, sizeof(ShipSaveData));
188+
if (j.contains("shipSaveData")) {
189+
j["shipSaveData"].get_to(save.shipSaveData);
190+
191+
// Migrate old saves that don't have achievement data
192+
bool hasMissingAchievementData = save.shipSaveData.features.achievements &&
193+
!j["shipSaveData"].contains("achievementSaveData");
194+
if (hasMissingAchievementData) {
195+
ReconstructAchievementData(save, save.shipSaveData.achievementSaveData);
196+
}
197+
}
198+
}
199+
200+
inline void LoadSaveFileV2(const json& j, SaveFile& save) {
141201
json capPosJson = GetSafeEntry<json>(j, "capPos", json::object());
142202
save.capLevel = GetSafeEntry(j, "capLevel", 0);
143203
save.capArea = GetSafeEntry(j, "capArea", 0);
@@ -156,6 +216,7 @@ inline void LoadSaveFileV1(const json& j, SaveFile& save) {
156216
save.courseCoinScores[i] = GetSafeEntry<u8>(coinsJson, entries[i], static_cast<u8>(0));
157217
}
158218

219+
memset(&save.shipSaveData, 0, sizeof(ShipSaveData));
159220
if (j.contains("shipSaveData")) {
160221
j["shipSaveData"].get_to(save.shipSaveData);
161222
}
@@ -164,7 +225,9 @@ inline void LoadSaveFileV1(const json& j, SaveFile& save) {
164225
inline void from_json(const json& j, SaveFile& save) {
165226
uint32_t version = GetSafeEntry<uint32_t>(j, "version", 1u);
166227
if (version == 1) {
167-
LoadSaveFileV1(j, save);
228+
LoadSaveFileLegacy(j, save);
229+
} else if (version == 2) {
230+
LoadSaveFileV2(j, save);
168231
}
169232
}
170233

0 commit comments

Comments
 (0)