Fix save corruption from non null-terminated cstrings (variation) (#230)

* verify save slots better; allow global.sav to work again

* fix save corruption and restore global.sav

* add comments
This commit is contained in:
Archez
2024-05-22 09:05:01 -05:00
committed by Garrett Cox
parent 90cb488263
commit ab19d6417d
3 changed files with 37 additions and 39 deletions
+25 -27
View File
@@ -17,15 +17,18 @@ void to_json(json& j, const ItemEquips& itemEquips) {
void from_json(const json& j, ItemEquips& itemEquips) {
j.at("equipment").get_to(itemEquips.equipment);
// buttonItems and cButtonSlots are arrays of arrays, so we need to manually parse them
for (int x = 0; x < 4; x++) {
for (int y = 0; y < 4; y++) {
itemEquips.buttonItems[x][y] = j.at("buttonItems")[x][y].get<u8>();
itemEquips.cButtonSlots[x][y] = j.at("cButtonSlots")[x][y].get<u8>();
}
for (int i = 0; i < ARRAY_COUNT(itemEquips.buttonItems); i++) {
j.at("buttonItems").at(i).get_to(itemEquips.buttonItems[i]);
j.at("cButtonSlots").at(i).get_to(itemEquips.cButtonSlots[i]);
}
}
void to_json(json& j, const Inventory& inventory) {
// Setup and copy u8 arrays to avoid json treating char[] as strings
// These char[] are not null-terminated, so saving as strings causes overflow/corruption
uint8_t dekuPlaygroundPlayerName[3][8];
memcpy(dekuPlaygroundPlayerName, inventory.dekuPlaygroundPlayerName, sizeof(dekuPlaygroundPlayerName));
j = json{
{ "items", inventory.items },
{ "ammo", inventory.ammo },
@@ -35,7 +38,7 @@ void to_json(json& j, const Inventory& inventory) {
{ "dungeonKeys", inventory.dungeonKeys },
{ "defenseHearts", inventory.defenseHearts },
{ "strayFairies", inventory.strayFairies },
{ "dekuPlaygroundPlayerName", inventory.dekuPlaygroundPlayerName },
{ "dekuPlaygroundPlayerName", dekuPlaygroundPlayerName },
};
}
@@ -48,12 +51,9 @@ void from_json(const json& j, Inventory& inventory) {
j.at("dungeonKeys").get_to(inventory.dungeonKeys);
j.at("defenseHearts").get_to(inventory.defenseHearts);
j.at("strayFairies").get_to(inventory.strayFairies);
// dekuPlaygroundPlayerName is an array of char arrays, so we need to manually parse it
for (int i = 0; i < 3; i++) {
std::string name = j.at("dekuPlaygroundPlayerName")[i].get<std::string>();
for (int j = 0; j < 8; j++) {
inventory.dekuPlaygroundPlayerName[i][j] = name[j];
}
// dekuPlaygroundPlayerName is an array of arrays, so we need to manually parse it
for (int i = 0; i < ARRAY_COUNT(inventory.dekuPlaygroundPlayerName); i++) {
j.at("dekuPlaygroundPlayerName").at(i).get_to(inventory.dekuPlaygroundPlayerName[i]);
}
}
@@ -80,10 +80,17 @@ void from_json(const json& j, PermanentSceneFlags& permanentSceneFlags) {
}
void to_json(json& j, const SavePlayerData& savePlayerData) {
// Setup and copy u8 arrays to avoid json treating char[] as strings
// These char[] are not null-terminated, so saving as strings causes overflow/corruption
u8 newf[6];
u8 playerName[8];
memcpy(newf, savePlayerData.newf, sizeof(newf));
memcpy(playerName, savePlayerData.playerName, sizeof(playerName));
j = json{
{ "newf", savePlayerData.newf },
{ "newf", newf },
{ "threeDayResetCount", savePlayerData.threeDayResetCount },
{ "playerName", savePlayerData.playerName },
{ "playerName", playerName },
{ "healthCapacity", savePlayerData.healthCapacity },
{ "health", savePlayerData.health },
{ "magicLevel", savePlayerData.magicLevel },
@@ -103,16 +110,9 @@ void to_json(json& j, const SavePlayerData& savePlayerData) {
}
void from_json(const json& j, SavePlayerData& savePlayerData) {
// newf is an array of chars, so we need to manually parse it
std::string newf = j.at("newf").get<std::string>();
for (int i = 0; i < 6; i++) {
savePlayerData.newf[i] = newf[i];
}
j.at("newf").get_to(savePlayerData.newf);
j.at("threeDayResetCount").get_to(savePlayerData.threeDayResetCount);
std::string playerName = j.at("playerName").get<std::string>();
for (int i = 0; i < 8; i++) {
savePlayerData.playerName[i] = playerName[i];
}
j.at("playerName").get_to(savePlayerData.playerName);
j.at("healthCapacity").get_to(savePlayerData.healthCapacity);
j.at("health").get_to(savePlayerData.health);
j.at("magicLevel").get_to(savePlayerData.magicLevel);
@@ -224,10 +224,8 @@ void from_json(const json& j, SaveInfo& saveInfo) {
j.at("bombersCaughtNum").get_to(saveInfo.bombersCaughtNum);
j.at("bombersCaughtOrder").get_to(saveInfo.bombersCaughtOrder);
// lotteryCodes is an array of arrays, so we need to manually parse it
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
saveInfo.lotteryCodes[x][y] = j.at("lotteryCodes")[x][y].get<s8>();
}
for (int i = 0; i < ARRAY_COUNT(saveInfo.lotteryCodes); i++) {
j.at("lotteryCodes").at(i).get_to(saveInfo.lotteryCodes[i]);
}
j.at("spiderHouseMaskOrder").get_to(saveInfo.spiderHouseMaskOrder);
j.at("bomberCode").get_to(saveInfo.bomberCode);
+10 -12
View File
@@ -1587,18 +1587,17 @@ extern "C" void BenSysFlashrom_WriteData(u8* saveBuffer, u32 pageNum, u32 pageCo
FlashSlotFile flashSlotFile = FLASH_SLOT_FILE_UNAVAILABLE;
bool isBackup = false;
for (u32 i = 0; i < ARRAY_COUNT(gFlashSaveStartPages) - 1; i++) {
if (pageNum == gFlashSaveStartPages[i]) {
// Verify that the requested pages align with expected values
if (pageNum == (u32)gFlashSaveStartPages[i] &&
(pageCount == (u32)gFlashSaveNumPages[i] || pageCount == (u32)gFlashSpecialSaveNumPages[i])) {
flashSlotFile = static_cast<FlashSlotFile>(i);
break;
}
}
// Exclude debug file from saving
if (flashSlotFile == FLASH_SLOT_FILE_UNAVAILABLE || gSaveContext.fileNum == 255) {
return;
}
switch (flashSlotFile) {
case FLASH_SLOT_FILE_UNAVAILABLE:
return;
case FLASH_SLOT_FILE_1_NEW_CYCLE_BACKUP:
case FLASH_SLOT_FILE_2_NEW_CYCLE_BACKUP:
isBackup = true;
@@ -1653,17 +1652,17 @@ extern "C" s32 BenSysFlashrom_ReadData(void* saveBuffer, u32 pageNum, u32 pageCo
FlashSlotFile flashSlotFile = FLASH_SLOT_FILE_UNAVAILABLE;
bool isBackup = false;
for (u32 i = 0; i < ARRAY_COUNT(gFlashSaveStartPages) - 1; i++) {
if (pageNum == gFlashSaveStartPages[i]) {
// Verify that the requested pages align with expected values
if (pageNum == (u32)gFlashSaveStartPages[i] &&
(pageCount == (u32)gFlashSaveNumPages[i] || pageCount == (u32)gFlashSpecialSaveNumPages[i])) {
flashSlotFile = static_cast<FlashSlotFile>(i);
break;
}
}
if (flashSlotFile == FLASH_SLOT_FILE_UNAVAILABLE) {
return -1;
}
switch (flashSlotFile) {
case FLASH_SLOT_FILE_UNAVAILABLE:
return -1;
case FLASH_SLOT_FILE_1_NEW_CYCLE_BACKUP:
case FLASH_SLOT_FILE_2_NEW_CYCLE_BACKUP:
isBackup = true;
@@ -1712,7 +1711,6 @@ extern "C" s32 BenSysFlashrom_ReadData(void* saveBuffer, u32 pageNum, u32 pageCo
memcpy(saveBuffer, &saveOptions, sizeof(SaveOptions));
return 0;
break;
}
}
}
+2
View File
@@ -214,6 +214,7 @@ typedef struct Inventory {
/* 0x5A */ s8 dungeonKeys[9]; // "key_register"
/* 0x63 */ s8 defenseHearts;
/* 0x64 */ s8 strayFairies[10]; // "orange_fairy"
// 2S2H [Comment] These char[] are not null-terminated, they will not work correctly in string functions
/* 0x6E */ char dekuPlaygroundPlayerName[3][8]; // "degnuts_memory_name" Stores playerName (8 char) over (3 days) when getting a new high score
} Inventory; // size = 0x88
@@ -262,6 +263,7 @@ typedef struct SaveOptions {
} SaveOptions; // size = 0x6
typedef struct SavePlayerData {
// 2S2H [Comment] These char[] are not null-terminated, they will not work correctly in string functions
/* 0x00 */ char newf[6]; // "newf" Will always be "ZELDA3 for a valid save
/* 0x06 */ u16 threeDayResetCount; // "savect"
/* 0x08 */ char playerName[8]; // "player_name"