pokeheartgold/src/save.c
2024-11-12 09:37:12 +02:00

1399 lines
46 KiB
C

#include "save.h"
#include "global.h"
#include "heap.h"
#include "math_util.h"
#include "save_arrays.h"
#include "save_data_read_error.h"
#include "save_data_write_error.h"
#include "save_misc_data.h"
#include "system.h"
#include "unk_0202C034.h"
static BOOL saveWritten;
static SaveData *sSaveDataPtr;
static u32 Save_IsNewGame(SaveData *saveData);
static void Save_SetExtraChunksExist(SaveData *saveData);
static u32 Save_CalcNumModifiedPCBoxes(SaveData *saveData);
static void SaveFooterDebugPrn(struct SaveChunkFooter *footer);
static void DebugPrn_MirrorValid(BOOL unk);
static void SaveSlotCheck_InitDummy(struct SaveSlotCheck *check);
static u16 SaveArray_CalcCRC16MinusFooter(SaveData *saveData, const void *data, u32 size);
static u32 GetChunkOffsetFromCurrentSaveSlot(u32 slot, struct SaveSlotSpec *spec);
static struct SaveChunkFooter *GetSaveSectorFooterPtr(SaveData *saveData, void *data, int idx);
static BOOL ValidateSaveSectorFooter(SaveData *saveData, void *data, int idx);
static void SaveSlotCheck_InitFromSavedat(struct SaveSlotCheck *check, SaveData *saveData, void *data, int idx);
static void SaveSlot_BuildFooter(SaveData *saveData, void *data, int idx);
static int SaveCounterCompare(u32 stat1, u32 stat2);
static u32 SaveSlotCheckCompare(struct SaveSlotCheck *first, struct SaveSlotCheck *second, u32 *ret1_p, u32 *ret2_p);
static void Save_RecordWhichLatestGoodSector(SaveData *saveData, struct SaveSlotCheck *a1, struct SaveSlotCheck *a2, int idx);
static int Save_GetSaveFilesStatus(SaveData *saveData);
static void Save_CheckFrontierData(SaveData *saveData, int *err1, int *err2);
static BOOL FlashLoadSaveDataFromChunk(u32 slot, struct SaveSlotSpec *spec, void *dest);
static BOOL Save_LoadDynamicRegion(SaveData *saveData);
static int Save_WriteSlotAsync(SaveData *saveData, int idx, u8 slot);
static int Save_WriteChunkFooterAsync(SaveData *saveData, int idx, u8 slot);
static void Save_WriteManInit(SaveData *saveData, struct AsyncWriteManager *writeMan, int a2);
static int HandleWriteSaveAsync_NormalData(SaveData *saveData, struct AsyncWriteManager *writeMan);
static void Save_WriteManFinish(SaveData *saveData, struct AsyncWriteManager *writeMan, int a2);
static void CancelAsyncSave(SaveData *saveData, struct AsyncWriteManager *writeMan);
static int _NowWriteFlash(SaveData *saveData);
static int FlashClobberChunkFooter(SaveData *saveData, int spec, int sector);
static u32 GetSaveChunkSizePlusCRC(int idx);
static void SaveData_InitSubstructs(struct SaveArrayHeader *arr_hdr);
static void SaveData_InitSlotSpecs(struct SaveSlotSpec *slotSpecs, struct SaveArrayHeader *headers);
static void Save_InitDynamicRegion_Internal(u8 *dynamic_region, struct SaveArrayHeader *headers);
static void CreateChunkFooter(SaveData *saveData, void *data, int idx, u32 size);
static BOOL ValidateChunk(SaveData *saveData, void *data, int idx, u32 size);
static u32 SaveArray_GetFooterSaveNo(void *data, u32 size);
static void sub_020286B4(SaveData *saveData, int a1, u32 *a2, u32 *a3, u8 *a4);
static void sub_020286D4(SaveData *saveData, int a1, u32 a2, u32 a3, u8 a4);
static BOOL SaveDetectFlash(void);
static s32 FlashWriteChunk(u32 offset, void *data, u32 size);
static BOOL FlashLoadChunk(u32 offset, void *data, u32 size);
static void FlashWriteCommandCallback(void *arg);
static s32 FlashWriteChunkInternal(u32 offset, void *data, u32 size);
static BOOL WaitFlashWrite(s32 lockId, BOOL checkResult, BOOL *resultSuccess);
static void SaveErrorHandling(s32 lockId, int code);
static int HandleWriteSaveAsync_PCBoxes(SaveData *saveData, struct AsyncWriteManager *writeMan);
static int Save_WritePCBoxes(SaveData *saveData, struct AsyncWriteManager *writeMan);
static int Save_WriteNextPCBox(SaveData *saveData, struct SaveSlotSpec *spec, u8 slot);
static int Save_WritePCFooter(SaveData *saveData, struct SaveSlotSpec *spec, u8 slot);
static u32 Save_CalcPCBoxModifiedFlags(SaveData *saveData);
static u32 PCModifiedFlags_CountModifiedBoxes(u32 flags);
static u32 PCModifiedFlags_GetIndexOfNthModifiedBox(u32 flags, u8 last);
SaveData *SaveData_New(void) {
SaveData *ret;
int status;
int sp4;
int sp0;
ret = AllocFromHeap(HEAP_ID_1, sizeof(SaveData));
MI_CpuClearFast(ret, sizeof(SaveData));
sSaveDataPtr = ret;
ret->flashChipDetected = SaveDetectFlash();
ret->saveFileExists = FALSE;
ret->isNewGame = TRUE;
ret->sectorCleanFlag[0] = 1;
ret->sectorCleanFlag[1] = 1;
SaveData_InitSubstructs(ret->arrayHeaders);
SaveData_InitSlotSpecs(ret->saveSlotSpecs, ret->arrayHeaders);
status = Save_GetSaveFilesStatus(ret);
ret->statusFlags = 0;
switch (status) {
case LOAD_STATUS_IS_GOOD:
case LOAD_STATUS_SLOT_FAIL:
Save_LoadDynamicRegion(ret);
ret->saveFileExists = TRUE;
ret->isNewGame = FALSE;
if (status == LOAD_STATUS_SLOT_FAIL) {
ret->statusFlags |= 1;
} else {
ret->boxModifiedFlags = Save_GetPCBoxModifiedFlags(ret);
}
Save_CheckFrontierData(ret, &sp4, &sp0);
if (sp4 == 3) {
ret->statusFlags |= 8;
} else if (sp4 == 2) {
ret->statusFlags |= 4;
}
if (sp0 == 3) {
ret->statusFlags |= 0x20;
} else if (sp0 == 2) {
ret->statusFlags |= 0x10;
}
break;
case LOAD_STATUS_TOTAL_FAIL:
ret->statusFlags |= 2;
// fallthrough
case LOAD_STATUS_NOT_EXIST:
Save_InitDynamicRegion(ret);
break;
}
return ret;
}
SaveData *SaveData_Get(void) {
GF_ASSERT(sSaveDataPtr != NULL);
return sSaveDataPtr;
}
void *SaveArray_Get(SaveData *saveData, int id) {
GF_ASSERT(id < SAVE_BLOCK_NUM);
return (void *)&saveData->dynamic_region[saveData->arrayHeaders[id].offset];
}
const void *SaveArray_Const_Get(const SaveData *saveData, int id) {
return SaveArray_Get((SaveData *)saveData, id);
}
BOOL Save_DeleteAllData(SaveData *saveData) {
u8 *r6;
int i;
r6 = AllocFromHeapAtEnd(HEAP_ID_3, SAVE_SECTOR_SIZE);
Sys_SetSleepDisableFlag(1);
FlashClobberChunkFooter(saveData, 0, saveData->lastGoodSector == 0 ? 1 : 0);
FlashClobberChunkFooter(saveData, 1, saveData->lastGoodSector == 0 ? 1 : 0);
FlashClobberChunkFooter(saveData, 0, saveData->lastGoodSector);
FlashClobberChunkFooter(saveData, 1, saveData->lastGoodSector);
MI_CpuFillFast(r6, -1, SAVE_SECTOR_SIZE);
for (i = 0; i < 64; i++) {
FlashWriteChunk(i * SAVE_SECTOR_SIZE, r6, SAVE_SECTOR_SIZE);
FlashWriteChunk((i + 64) * SAVE_SECTOR_SIZE, r6, SAVE_SECTOR_SIZE);
}
FreeToHeap(r6);
Save_InitDynamicRegion(saveData);
saveData->saveFileExists = FALSE;
Sys_ClearSleepDisableFlag(1);
return TRUE;
}
BOOL SaveData_TryLoadOnContinue(SaveData *saveData) {
int sp4;
int sp0;
if (!saveData->flashChipDetected) {
return FALSE;
}
if (Save_LoadDynamicRegion(saveData)) {
saveData->saveFileExists = TRUE;
saveData->isNewGame = FALSE;
Save_CheckFrontierData(saveData, &sp4, &sp0);
Save_ResetPCBoxModifiedFlags(saveData);
return TRUE;
}
return FALSE;
}
int SaveGameNormal(SaveData *saveData) {
int ret;
if (!saveData->flashChipDetected) {
return WRITE_STATUS_TOTAL_FAIL;
}
if (saveData->isNewGame) {
Sys_SetSleepDisableFlag(1);
FlashClobberChunkFooter(saveData, 0, saveData->lastGoodSector == 0 ? 1 : 0);
FlashClobberChunkFooter(saveData, 1, saveData->lastGoodSector == 0 ? 1 : 0);
FlashClobberChunkFooter(saveData, 0, saveData->lastGoodSector);
FlashClobberChunkFooter(saveData, 1, saveData->lastGoodSector);
Sys_ClearSleepDisableFlag(1);
}
ret = _NowWriteFlash(saveData);
if (ret == WRITE_STATUS_SUCCESS) {
saveData->saveFileExists = TRUE;
saveData->isNewGame = FALSE;
}
return ret;
}
int Save_NowWriteFile_AfterMGInit(SaveData *saveData, int a1) {
int ret;
GF_ASSERT(a1 < 2);
GF_ASSERT(saveData->isNewGame == FALSE);
GF_ASSERT(saveData->saveFileExists == TRUE);
Save_PrepareForAsyncWrite(saveData, a1);
do {
ret = Save_WriteFileAsync(saveData);
} while (ret == WRITE_STATUS_CONTINUE || ret == WRITE_STATUS_NEXT);
return ret;
}
void Save_InitDynamicRegion(SaveData *saveData) {
saveData->isNewGame = TRUE;
saveData->sectorCleanFlag[0] = 1;
saveData->sectorCleanFlag[1] = 1;
Save_InitDynamicRegion_Internal(saveData->dynamic_region, saveData->arrayHeaders);
}
BOOL Save_FlashChipIsDetected(SaveData *saveData) {
return saveData->flashChipDetected;
}
u32 Save_GetStatusFlags(SaveData *saveData) {
return saveData->statusFlags;
}
void Save_ClearStatusFlags(SaveData *saveData) {
saveData->statusFlags = 0;
}
u32 Save_FileExists(SaveData *saveData) {
return saveData->saveFileExists;
}
static u32 Save_IsNewGame(SaveData *saveData) {
return saveData->isNewGame;
}
BOOL Save_CheckExtraChunksExist(SaveData *saveData) {
SAVE_MISC_DATA *misc = Save_Misc_Get(saveData);
return SaveMisc_CheckExtraChunksExist(misc);
}
static void Save_SetExtraChunksExist(SaveData *saveData) {
SAVE_MISC_DATA *misc = Save_Misc_Get(saveData);
SaveMisc_SetExtraChunksExist(misc);
}
BOOL Save_FileDoesNotBelongToPlayer(SaveData *saveData) {
return Save_IsNewGame(saveData) != 0 && Save_FileExists(saveData) != 0;
}
BOOL Save_NumModifiedPCBoxesIsMany(SaveData *saveData) {
return Save_CalcNumModifiedPCBoxes(saveData) >= 6;
}
void SetAllPCBoxesModified(void) {
Save_SetAllPCBoxesModified(sSaveDataPtr);
}
static u32 Save_CalcNumModifiedPCBoxes(SaveData *saveData) {
return PCModifiedFlags_CountModifiedBoxes(Save_CalcPCBoxModifiedFlags(saveData));
}
void Save_PrepareForAsyncWrite(SaveData *saveData, int a1) {
Save_WriteManInit(saveData, &saveData->asyncWriteMan, a1);
}
int Save_WriteFileAsync(SaveData *saveData) {
int ret;
if (saveData->asyncWriteMan.curSector == 1) {
ret = HandleWriteSaveAsync_PCBoxes(saveData, &saveData->asyncWriteMan);
} else {
ret = HandleWriteSaveAsync_NormalData(saveData, &saveData->asyncWriteMan);
}
if (!(ret == WRITE_STATUS_CONTINUE || ret == WRITE_STATUS_NEXT)) {
Save_WriteManFinish(saveData, &saveData->asyncWriteMan, ret);
}
return ret;
}
void Save_Cancel(SaveData *saveData) {
CancelAsyncSave(saveData, &saveData->asyncWriteMan);
}
static void SaveFooterDebugPrn(struct SaveChunkFooter *footer) {
#pragma unused(footer)
}
static void DebugPrn_MirrorValid(BOOL unk) {
#pragma unused(unk)
}
static void SaveSlotCheck_InitDummy(struct SaveSlotCheck *check) {
check->valid = FALSE;
check->count = 0;
}
u16 SaveArray_CalcCRC16(SaveData *saveData, const void *data, u32 size) {
#pragma unused(saveData)
return GF_CalcCRC16(data, size);
}
static u16 SaveArray_CalcCRC16MinusFooter(SaveData *saveData, const void *data, u32 size) {
#pragma unused(saveData)
return GF_CalcCRC16(data, size - sizeof(struct SaveArrayFooter));
}
static u32 GetChunkOffsetFromCurrentSaveSlot(u32 slot, struct SaveSlotSpec *spec) {
u32 adrs;
if (slot == 0) {
adrs = 0;
} else {
adrs = 0x40000;
}
return adrs + spec->offset;
}
static struct SaveChunkFooter *GetSaveSectorFooterPtr(SaveData *saveData, void *data, int idx) {
u8 *ret;
struct SaveSlotSpec *spec;
spec = &saveData->saveSlotSpecs[idx];
ret = (u8 *)data + spec->offset;
GF_ASSERT(spec->size != 0);
return (struct SaveChunkFooter *)(ret + spec->size - sizeof(struct SaveChunkFooter));
}
static BOOL ValidateSaveSectorFooter(SaveData *saveData, void *data, int idx) {
struct SaveSlotSpec *spec;
struct SaveChunkFooter *footer;
u32 offset;
spec = &saveData->saveSlotSpecs[idx];
footer = GetSaveSectorFooterPtr(saveData, data, idx);
offset = spec->offset;
SaveFooterDebugPrn(footer);
if (footer->size != spec->size) {
return FALSE;
}
if (footer->magic != SAVE_CHUNK_MAGIC) {
return FALSE;
}
if (footer->slot != idx) {
return FALSE;
}
return SaveArray_CalcCRC16MinusFooter(saveData, (u8 *)data + offset, spec->size) == footer->crc;
}
static void SaveSlotCheck_InitFromSavedat(struct SaveSlotCheck *check, SaveData *saveData, void *data, int idx) {
struct SaveChunkFooter *footer;
footer = GetSaveSectorFooterPtr(saveData, data, idx);
check->valid = ValidateSaveSectorFooter(saveData, data, idx);
if (check->valid) {
check->count = footer->count;
} else {
check->count = 0;
}
}
static void SaveSlot_BuildFooter(SaveData *saveData, void *data, int idx) {
struct SaveSlotSpec *spec;
struct SaveChunkFooter *footer;
u32 offset;
spec = &saveData->saveSlotSpecs[idx];
footer = GetSaveSectorFooterPtr(saveData, data, idx);
offset = spec->offset;
footer->count = saveData->saveCounter;
footer->size = spec->size;
footer->magic = SAVE_CHUNK_MAGIC;
footer->slot = idx;
footer->crc = SaveArray_CalcCRC16MinusFooter(saveData, (u8 *)data + offset, spec->size);
SaveFooterDebugPrn(footer);
}
static int SaveCounterCompare(u32 stat1, u32 stat2) {
if (stat1 == -1 && stat2 == 0) {
return -1;
}
if (stat1 == 0 && stat2 == -1) {
return 1;
}
if (stat1 > stat2) {
return 1;
}
return -((stat1 < stat2) ? 1 : 0);
}
static u32 SaveSlotCheckCompare(struct SaveSlotCheck *first, struct SaveSlotCheck *second, u32 *ret1_p, u32 *ret2_p) {
int r0;
r0 = SaveCounterCompare(first->count, second->count);
if (first->valid && second->valid) {
if (r0 > 0) {
*ret1_p = 0;
*ret2_p = 1;
} else if (r0 < 0) {
*ret1_p = 1;
*ret2_p = 0;
} else {
*ret1_p = 0;
*ret2_p = 1;
}
return 2;
}
if (first->valid && !second->valid) {
*ret1_p = 0;
*ret2_p = 2;
return 1;
}
if (!first->valid && second->valid) {
*ret1_p = 1;
*ret2_p = 2;
return 1;
}
*ret1_p = 2;
*ret2_p = 2;
return 0;
}
static void Save_RecordWhichLatestGoodSector(SaveData *saveData, struct SaveSlotCheck *checks_main, struct SaveSlotCheck *checks_sub, int idx) {
#pragma unused(checks_sub)
saveData->saveCounter = checks_main[idx].count;
saveData->lastGoodSector = idx;
}
static int Save_GetSaveFilesStatus(SaveData *saveData) {
u8 *data1;
u8 *data2;
struct SaveSlotCheck checks_main[2];
struct SaveSlotCheck checks_sub[2];
u32 newer_main;
u32 newer_sub;
u32 older_main;
u32 older_sub;
u32 __older_main;
u32 numGood_main;
u32 numGood_sub;
u32 __newer_main;
data1 = AllocFromHeapAtEnd(HEAP_ID_3, SAVE_PAGE_MAX * SAVE_SECTOR_SIZE);
data2 = AllocFromHeapAtEnd(HEAP_ID_3, SAVE_PAGE_MAX * SAVE_SECTOR_SIZE);
if (FlashLoadChunk(0 * 0x40000, data1, SAVE_PAGE_MAX * SAVE_SECTOR_SIZE)) {
SaveSlotCheck_InitFromSavedat(&checks_main[0], saveData, data1, 0);
SaveSlotCheck_InitFromSavedat(&checks_sub[0], saveData, data1, 1);
} else {
SaveSlotCheck_InitDummy(&checks_main[0]);
SaveSlotCheck_InitDummy(&checks_sub[0]);
}
if (FlashLoadChunk(1 * 0x40000, data2, SAVE_PAGE_MAX * SAVE_SECTOR_SIZE)) {
SaveSlotCheck_InitFromSavedat(&checks_main[1], saveData, data2, 0);
SaveSlotCheck_InitFromSavedat(&checks_sub[1], saveData, data2, 1);
} else {
SaveSlotCheck_InitDummy(&checks_main[1]);
SaveSlotCheck_InitDummy(&checks_sub[1]);
}
FreeToHeap(data1);
FreeToHeap(data2);
numGood_main = SaveSlotCheckCompare(&checks_main[0], &checks_main[1], &newer_main, &older_main);
__older_main = older_main;
__newer_main = newer_main;
numGood_sub = SaveSlotCheckCompare(&checks_sub[0], &checks_sub[1], &newer_sub, &older_sub);
DebugPrn_MirrorValid(checks_main[0].valid);
DebugPrn_MirrorValid(checks_main[1].valid);
DebugPrn_MirrorValid(checks_sub[0].valid);
DebugPrn_MirrorValid(checks_sub[1].valid);
if (numGood_main == 0 && numGood_sub == 0) {
return LOAD_STATUS_NOT_EXIST;
}
if (numGood_main == 0 || numGood_sub == 0) {
return LOAD_STATUS_TOTAL_FAIL;
}
GF_ASSERT(__newer_main != 2);
if (numGood_main == 2 && numGood_sub == 2) {
if (checks_main[__newer_main].count == checks_sub[__newer_main].count) {
Save_RecordWhichLatestGoodSector(saveData, checks_main, checks_sub, __newer_main);
saveData->sectorCleanFlag[0] = 0;
saveData->sectorCleanFlag[1] = 0;
return LOAD_STATUS_IS_GOOD;
}
if (checks_main[__older_main].count != checks_sub[__older_main].count) {
return LOAD_STATUS_TOTAL_FAIL;
}
Save_RecordWhichLatestGoodSector(saveData, checks_main, checks_sub, __older_main);
return LOAD_STATUS_SLOT_FAIL;
}
if (numGood_main == 1 && numGood_sub == 2) {
if (checks_main[__newer_main].count == checks_sub[__newer_main].count) {
Save_RecordWhichLatestGoodSector(saveData, checks_main, checks_sub, __newer_main);
return LOAD_STATUS_SLOT_FAIL;
}
return LOAD_STATUS_TOTAL_FAIL;
}
if (numGood_main == 2 && numGood_sub == 1) {
if (checks_main[__newer_main].count == checks_sub[__newer_main].count) {
Save_RecordWhichLatestGoodSector(saveData, checks_main, checks_sub, __newer_main);
return LOAD_STATUS_IS_GOOD;
}
if (__older_main == 2) {
return LOAD_STATUS_TOTAL_FAIL;
}
if (checks_main[__older_main].count == checks_sub[__older_main].count) {
Save_RecordWhichLatestGoodSector(saveData, checks_main, checks_sub, __older_main);
return LOAD_STATUS_SLOT_FAIL;
}
return LOAD_STATUS_TOTAL_FAIL;
}
if (numGood_main == 1 && numGood_sub == 1) {
if (newer_main == newer_sub) {
GF_ASSERT(checks_main[newer_main].count == checks_sub[newer_sub].count);
Save_RecordWhichLatestGoodSector(saveData, checks_main, checks_sub, newer_main);
saveData->sectorCleanFlag[newer_main] = 0;
return LOAD_STATUS_IS_GOOD;
}
}
return LOAD_STATUS_TOTAL_FAIL;
}
static void Save_CheckFrontierData(SaveData *saveData, int *err1, int *err2) {
SAVE_MISC_DATA *misc;
int sp14;
int sp10;
u32 sp0C;
u32 sp08;
u8 sp04;
int i;
misc = Save_Misc_Get(saveData);
*err1 = 1;
*err2 = 1;
if (Save_CheckExtraChunksExist(saveData)) {
sub_0202AC38(misc, 1, &sp0C, &sp08, &sp04);
if (sp0C != -1 || sp08 != -1) {
FreeToHeap(sub_020284A4(saveData, HEAP_ID_3, 1, &sp14, &sp10));
if (sp14 == 2) {
*err1 = 3;
} else if (sp14 == 1 && sp10 == 1) {
*err1 = 2;
}
}
for (i = 2; i <= 5; i++) {
sub_0202AC38(misc, i, &sp0C, &sp08, &sp04);
if (sp0C != -1 || sp08 != -1) {
FreeToHeap(sub_020284A4(saveData, HEAP_ID_3, i, &sp14, &sp10));
if (sp14 == 2) {
*err2 = 3;
} else if (sp14 == 1 && sp10 == 1 && *err2 != 3) {
*err2 = 2;
}
}
}
}
}
static BOOL FlashLoadSaveDataFromChunk(u32 slot, struct SaveSlotSpec *spec, void *dest) {
return FlashLoadChunk(GetChunkOffsetFromCurrentSaveSlot(slot, spec), (u8 *)dest + spec->offset, spec->size);
}
static BOOL Save_LoadDynamicRegion(SaveData *saveData) {
int i;
u8 *data;
u32 pc_offs;
u32 pc_size;
struct SaveSlotSpec *specs = saveData->saveSlotSpecs;
for (i = 0; i < 2; i++) {
data = saveData->dynamic_region;
if (!FlashLoadSaveDataFromChunk(saveData->lastGoodSector, &saveData->saveSlotSpecs[i], saveData->dynamic_region)) {
return FALSE;
}
if (!ValidateSaveSectorFooter(saveData, saveData->dynamic_region, i)) {
return FALSE;
}
}
for (i = 0; i < SAVE_BLOCK_NUM; i++) {
saveData->arrayHeaders[i].crc = GF_CalcCRC16(SaveArray_Get(saveData, i), saveData->arrayHeaders[i].size);
}
pc_offs = saveData->saveSlotSpecs[1].offset;
pc_size = PCStorage_GetSizeOfBox() * PCStorage_GetNumBoxes();
saveData->pcStorageLastCRC = GF_CalcCRC16(data + pc_offs, pc_size);
Save_Frontier_Load(saveData);
sub_0202C6FC(saveData);
return TRUE;
}
static int Save_WriteSlotAsync(SaveData *saveData, int idx, u8 slot) {
struct SaveSlotSpec *spec;
spec = &saveData->saveSlotSpecs[idx];
SaveSlot_BuildFooter(saveData, saveData->dynamic_region, idx);
return FlashWriteChunkInternal(GetChunkOffsetFromCurrentSaveSlot(slot, spec), saveData->dynamic_region + spec->offset, spec->size - sizeof(struct SaveChunkFooter));
}
static int Save_WriteChunkFooterAsync(SaveData *saveData, int idx, u8 slot) {
struct SaveSlotSpec *spec;
u32 size;
spec = &saveData->saveSlotSpecs[idx];
size = spec->size;
return FlashWriteChunkInternal(GetChunkOffsetFromCurrentSaveSlot(slot, spec) + size - sizeof(struct SaveChunkFooter), saveData->dynamic_region + spec->offset + size - sizeof(struct SaveChunkFooter), sizeof(struct SaveChunkFooter));
}
static void Save_WriteManInit(SaveData *saveData, struct AsyncWriteManager *writeMan, int a2) {
#pragma unused(a2)
sub_0202C714(saveData);
Save_Frontier_Commit(saveData);
writeMan->state = 0;
writeMan->state_sub = 0;
writeMan->rollbackCounter = 0;
writeMan->waitingAsync = FALSE;
writeMan->rollbackCounter = 1;
writeMan->count = saveData->saveCounter;
saveData->saveCounter++;
writeMan->unk_4 = 0;
writeMan->curSector = 0;
writeMan->numSectors = 2;
Sys_SetSleepDisableFlag(1);
}
static int HandleWriteSaveAsync_NormalData(SaveData *saveData, struct AsyncWriteManager *writeMan) {
BOOL result;
switch (writeMan->state) {
case 0:
writeMan->lockId = Save_WriteSlotAsync(saveData, writeMan->curSector, saveData->lastGoodSector == 0);
writeMan->waitingAsync = TRUE;
writeMan->state++;
// fallthrough
case 1:
if (!WaitFlashWrite(writeMan->lockId, writeMan->waitingAsync, &result)) {
break;
}
writeMan->waitingAsync = FALSE;
if (!result) {
return WRITE_STATUS_TOTAL_FAIL;
}
writeMan->state++;
if (writeMan->curSector + 1 == writeMan->numSectors) {
return WRITE_STATUS_NEXT;
}
// fallthrough
case 2:
writeMan->lockId = Save_WriteChunkFooterAsync(saveData, writeMan->curSector, saveData->lastGoodSector == 0);
writeMan->waitingAsync = TRUE;
writeMan->state++;
// fallthrough
case 3:
if (!WaitFlashWrite(writeMan->lockId, writeMan->waitingAsync, &result)) {
break;
}
writeMan->waitingAsync = FALSE;
if (!result) {
return WRITE_STATUS_TOTAL_FAIL;
}
if (++writeMan->curSector == writeMan->numSectors) {
return WRITE_STATUS_SUCCESS;
}
writeMan->state = 0;
break;
}
return WRITE_STATUS_CONTINUE;
}
static void Save_WriteManFinish(SaveData *saveData, struct AsyncWriteManager *writeMan, int a2) {
saveData->numModifiedBoxes = 0;
saveData->nextBoxToWrite = 0;
if (a2 == 3) {
if (writeMan->rollbackCounter) {
saveData->saveCounter = writeMan->count;
}
} else {
saveData->boxModifiedFlags = Save_GetPCBoxModifiedFlags(saveData);
saveData->pcStorageLastCRC = saveData->pcStorageCRC;
Save_ResetPCBoxModifiedFlags(saveData);
saveData->sectorCleanFlag[saveData->lastGoodSector == 0] = 0;
saveData->lastGoodSector = saveData->lastGoodSector == 0;
saveData->saveFileExists = TRUE;
saveData->isNewGame = FALSE;
}
Sys_ClearSleepDisableFlag(1);
}
static void CancelAsyncSave(SaveData *saveData, struct AsyncWriteManager *writeMan) {
if (writeMan->rollbackCounter) {
saveData->saveCounter = writeMan->count;
}
if (!CARD_TryWaitBackupAsync()) {
CARD_CancelBackupAsync();
}
if (writeMan->waitingAsync) {
CARD_UnlockBackup(writeMan->lockId);
OS_ReleaseLockID(writeMan->lockId);
writeMan->waitingAsync = FALSE;
}
Sys_ClearSleepDisableFlag(1);
}
static int _NowWriteFlash(SaveData *saveData) {
struct AsyncWriteManager writeManager;
int ret;
Save_WriteManInit(saveData, &writeManager, 2);
do {
if (writeManager.curSector != 1) {
ret = HandleWriteSaveAsync_NormalData(saveData, &writeManager);
} else {
ret = HandleWriteSaveAsync_PCBoxes(saveData, &writeManager);
}
} while (ret == WRITE_STATUS_CONTINUE || ret == WRITE_STATUS_NEXT);
Save_WriteManFinish(saveData, &writeManager, ret);
return ret;
}
static int FlashClobberChunkFooter(SaveData *saveData, int spec, int sector) {
struct SaveChunkFooter sp0;
struct SaveSlotSpec *slotSpec;
slotSpec = &saveData->saveSlotSpecs[spec];
MI_CpuFill8(&sp0, 0xFF, sizeof(struct SaveChunkFooter));
return FlashWriteChunk(GetChunkOffsetFromCurrentSaveSlot(sector, slotSpec) + slotSpec->size - sizeof(struct SaveChunkFooter), &sp0, sizeof(struct SaveChunkFooter));
}
static u32 GetSaveChunkSizePlusCRC(int idx) {
u32 size;
const struct SaveChunkHeader *hdr;
hdr = gSaveChunkHeaders;
GF_ASSERT(idx < gNumSaveChunkHeaders);
size = hdr[idx].sizeFunc();
size = ((size + 3) & ~3) + 4;
return size;
}
static void SaveData_InitSubstructs(struct SaveArrayHeader *arr_hdr) {
int i;
const struct SaveChunkHeader *hdr;
int adrs;
hdr = gSaveChunkHeaders;
adrs = 0;
GF_ASSERT(gNumSaveChunkHeaders == SAVE_BLOCK_NUM);
for (i = 0; i < gNumSaveChunkHeaders; i++) {
GF_ASSERT(i == hdr[i].id);
arr_hdr[i].id = hdr[i].id;
arr_hdr[i].size = GetSaveChunkSizePlusCRC(i);
arr_hdr[i].offset = adrs;
arr_hdr[i].crc = 0;
arr_hdr[i].slot = hdr[i].block;
adrs += arr_hdr[i].size;
if (i == gNumSaveChunkHeaders - 1 || hdr[i].block != hdr[i + 1].block) {
adrs += sizeof(struct SaveChunkFooter);
if (hdr[i].block != hdr[i + 1].block && i + 1 < gNumSaveChunkHeaders && (adrs % 0x100) != 0) {
adrs += 0x100 - (adrs % 0x100);
}
}
}
GF_ASSERT(adrs <= SAVE_PAGE_MAX * SAVE_SECTOR_SIZE);
}
static void SaveData_InitSlotSpecs(struct SaveSlotSpec *slotSpecs, struct SaveArrayHeader *headers) {
int i;
int adrs;
int npage;
int j;
npage = 0;
adrs = 0;
j = 0;
for (i = 0; i < 2; i++) {
slotSpecs[i].id = i;
slotSpecs[i].size = 0;
for (; i == headers[j].slot && j < gNumSaveChunkHeaders; j++) {
slotSpecs[i].size += headers[j].size;
}
slotSpecs[i].size += sizeof(struct SaveChunkFooter);
slotSpecs[i].firstPage = npage;
slotSpecs[i].offset = adrs;
slotSpecs[i].numPages = (slotSpecs[i].size + SAVE_SECTOR_SIZE - 1) / SAVE_SECTOR_SIZE;
npage += slotSpecs[i].numPages;
adrs += slotSpecs[i].size;
if (adrs % 0x100 != 0) {
adrs += (0x100 - (adrs % 0x100));
}
}
GF_ASSERT(npage == slotSpecs[1].firstPage + slotSpecs[1].numPages);
GF_ASSERT(npage <= SAVE_PAGE_MAX);
}
static void Save_InitDynamicRegion_Internal(u8 *dynamic_region, struct SaveArrayHeader *headers) {
const struct SaveChunkHeader *chunkHeaders;
int i;
u32 adrs;
void *ptr;
chunkHeaders = gSaveChunkHeaders;
MI_CpuClearFast(dynamic_region, SAVE_PAGE_MAX * SAVE_SECTOR_SIZE);
for (i = 0; i < gNumSaveChunkHeaders; i++) {
adrs = headers[i].offset;
ptr = dynamic_region + adrs;
MI_CpuClearFast(ptr, headers[i].size);
chunkHeaders[i].initFunc(ptr);
}
}
void Save_WipeExtraChunks(SaveData *saveData) {
const struct ExtraSaveChunkHeader *chunkHeaders;
int i;
void *data;
int status;
chunkHeaders = gExtraSaveChunkHeaders;
if (Save_CheckExtraChunksExist(saveData) == 1) {
return;
}
for (i = 0; i < gNumExtraSaveChunkHeaders; i++) {
if (chunkHeaders[i].id != 0) {
data = ReadExtraSaveChunk(saveData, HEAP_ID_3, chunkHeaders[i].id, &status);
GF_ASSERT(data != NULL);
MI_CpuClear8(data, chunkHeaders[i].sizeFunc());
chunkHeaders[i].initFunc(data);
WriteExtraSaveChunk(saveData, chunkHeaders[i].id, data);
FreeToHeap(data);
}
}
Save_SetExtraChunksExist(saveData);
}
static void CreateChunkFooter(SaveData *saveData, void *data, int idx, u32 size) {
struct SaveArrayFooter *footer;
footer = (struct SaveArrayFooter *)((u8 *)data + size);
footer->magic = SAVE_CHUNK_MAGIC;
footer->saveno = saveData->lastGoodSaveNo + 1;
footer->size = size;
footer->idx = idx;
footer->crc = GF_CalcCRC16(data, size + offsetof(struct SaveArrayFooter, crc));
}
static BOOL ValidateChunk(SaveData *saveData, void *data, int idx, u32 size) {
#pragma unused(saveData)
struct SaveArrayFooter *footer;
footer = (struct SaveArrayFooter *)((u8 *)data + size);
if (footer->magic != SAVE_CHUNK_MAGIC) {
return FALSE;
}
if (footer->size != size) {
return FALSE;
}
if (footer->idx != idx) {
return FALSE;
}
return footer->crc == GF_CalcCRC16(data, size + offsetof(struct SaveArrayFooter, crc));
}
static u32 SaveArray_GetFooterSaveNo(void *data, u32 size) {
struct SaveArrayFooter *footer;
footer = (struct SaveArrayFooter *)((u8 *)data + size);
return footer->saveno;
}
int WriteExtraSaveChunk(SaveData *saveData, int idx, void *data) {
const struct ExtraSaveChunkHeader *hdr;
u32 size;
int ret;
Sys_SetSleepDisableFlag(1);
GF_ASSERT(idx < gNumExtraSaveChunkHeaders);
hdr = &gExtraSaveChunkHeaders[idx];
GF_ASSERT(hdr->id == idx);
size = hdr->sizeFunc() + sizeof(struct SaveArrayFooter);
if (saveData->lastGoodSaveSlot == 1) {
CreateChunkFooter(saveData, data, idx, hdr->sizeFunc());
ret = FlashWriteChunk(hdr->sector * SAVE_SECTOR_SIZE, data, size);
GF_ASSERT(ValidateChunk(saveData, data, idx, hdr->sizeFunc()) == TRUE);
CreateChunkFooter(saveData, data, idx, hdr->sizeFunc());
ret |= FlashWriteChunk((hdr->sector + 64) * SAVE_SECTOR_SIZE, data, size);
GF_ASSERT(ValidateChunk(saveData, data, idx, hdr->sizeFunc()) == TRUE);
} else {
CreateChunkFooter(saveData, data, idx, hdr->sizeFunc());
ret = FlashWriteChunk((hdr->sector + 64) * SAVE_SECTOR_SIZE, data, size);
GF_ASSERT(ValidateChunk(saveData, data, idx, hdr->sizeFunc()) == TRUE);
CreateChunkFooter(saveData, data, idx, hdr->sizeFunc());
ret |= FlashWriteChunk(hdr->sector * SAVE_SECTOR_SIZE, data, size);
GF_ASSERT(ValidateChunk(saveData, data, idx, hdr->sizeFunc()) == TRUE);
}
if (ret == 1) {
Sys_ClearSleepDisableFlag(1);
return 2;
}
Sys_ClearSleepDisableFlag(1);
return 3;
}
int sub_02028230(SaveData *saveData, int idx, void *data) {
const struct ExtraSaveChunkHeader *hdr;
u32 size;
int ret;
u32 sp14;
u32 sp10;
u8 sp0C;
u32 r6;
Sys_SetSleepDisableFlag(1);
GF_ASSERT(idx < gNumExtraSaveChunkHeaders);
hdr = &gExtraSaveChunkHeaders[idx];
GF_ASSERT(hdr->id == idx);
size = hdr->sizeFunc() + sizeof(struct SaveArrayFooter);
sub_020286B4(saveData, idx, &sp14, &sp10, &sp0C);
do {
r6 = PRandom(sp14);
} while (r6 == -1);
sub_020286D4(saveData, idx, r6, sp14, sp0C ^ 1);
*(u32 *)data = r6;
if (sp0C == 1) {
CreateChunkFooter(saveData, data, idx, hdr->sizeFunc());
ret = FlashWriteChunk(hdr->sector * SAVE_SECTOR_SIZE, data, size);
GF_ASSERT(ValidateChunk(saveData, data, idx, hdr->sizeFunc()) == TRUE);
} else {
CreateChunkFooter(saveData, data, idx, hdr->sizeFunc());
ret = FlashWriteChunk((hdr->sector + 64) * SAVE_SECTOR_SIZE, data, size);
GF_ASSERT(ValidateChunk(saveData, data, idx, hdr->sizeFunc()) == TRUE);
}
if (ret == 1) {
Sys_ClearSleepDisableFlag(1);
return WRITE_STATUS_SUCCESS;
}
Sys_ClearSleepDisableFlag(1);
return WRITE_STATUS_TOTAL_FAIL;
}
void *ReadExtraSaveChunk(SaveData *saveData, HeapID heapId, int idx, int *ret_p) {
const struct ExtraSaveChunkHeader *hdr;
u32 size;
void *ret;
BOOL valid1;
BOOL valid2;
u32 saveno1;
u32 saveno2;
GF_ASSERT(idx < gNumExtraSaveChunkHeaders);
hdr = &gExtraSaveChunkHeaders[idx];
GF_ASSERT(hdr->id == idx);
size = hdr->sizeFunc() + sizeof(struct SaveArrayFooter);
ret = AllocFromHeap(heapId, size);
FlashLoadChunk(hdr->sector * SAVE_SECTOR_SIZE, ret, size);
valid1 = ValidateChunk(saveData, ret, idx, hdr->sizeFunc());
saveno1 = SaveArray_GetFooterSaveNo(ret, hdr->sizeFunc());
FlashLoadChunk((hdr->sector + 64) * SAVE_SECTOR_SIZE, ret, size);
valid2 = ValidateChunk(saveData, ret, idx, hdr->sizeFunc());
saveno2 = SaveArray_GetFooterSaveNo(ret, hdr->sizeFunc());
*ret_p = 1;
if (valid1 == TRUE && valid2 == FALSE) {
saveData->lastGoodSaveSlot = 0;
saveData->lastGoodSaveNo = saveno1;
FlashLoadChunk(hdr->sector * SAVE_SECTOR_SIZE, ret, size);
return ret;
}
if (valid1 == FALSE && valid2 == TRUE) {
saveData->lastGoodSaveSlot = 1;
saveData->lastGoodSaveNo = saveno2;
FlashLoadChunk((hdr->sector + 64) * SAVE_SECTOR_SIZE, ret, size);
return ret;
}
if (valid1 == TRUE && valid2 == TRUE) {
if (SaveCounterCompare(saveno1, saveno2) != -1) {
saveData->lastGoodSaveSlot = 0;
saveData->lastGoodSaveNo = saveno1;
FlashLoadChunk(hdr->sector * SAVE_SECTOR_SIZE, ret, size);
return ret;
} else {
saveData->lastGoodSaveSlot = 1;
saveData->lastGoodSaveNo = saveno2;
FlashLoadChunk((hdr->sector + 64) * SAVE_SECTOR_SIZE, ret, size);
return ret;
}
}
*ret_p = 2;
saveData->lastGoodSaveSlot = 0;
saveData->lastGoodSaveNo = 0;
return ret;
}
void *sub_020284A4(SaveData *saveData, HeapID heapId, int idx, int *ret_p, int *ret2_p) {
const struct ExtraSaveChunkHeader *hdr;
u32 sp2C;
u32 sp28;
u32 sp24;
u32 sp20;
u8 sp1C;
u32 size;
void *ret;
BOOL valid1;
BOOL valid2;
SAVE_MISC_DATA *misc;
misc = Save_Misc_Get(saveData);
GF_ASSERT(idx < gNumExtraSaveChunkHeaders);
GF_ASSERT(idx != 0);
hdr = &gExtraSaveChunkHeaders[idx];
GF_ASSERT(hdr->id == idx);
size = hdr->sizeFunc() + sizeof(struct SaveArrayFooter);
ret = AllocFromHeap(heapId, size);
sub_020286B4(saveData, idx, &sp24, &sp20, &sp1C);
FlashLoadChunk(hdr->sector * SAVE_SECTOR_SIZE, ret, size);
valid1 = ValidateChunk(saveData, ret, idx, hdr->sizeFunc());
MI_CpuCopy8(ret, &sp2C, sizeof(u32));
FlashLoadChunk((hdr->sector + 64) * SAVE_SECTOR_SIZE, ret, size);
valid2 = ValidateChunk(saveData, ret, idx, hdr->sizeFunc());
MI_CpuCopy8(ret, &sp28, sizeof(u32));
*ret_p = 1;
*ret2_p = 0;
if (valid1 == TRUE && valid2 == FALSE && sp24 == sp2C) {
if (sp1C == 1) {
sub_020286D4(saveData, idx, sp20, sp20, 0);
*ret2_p = 1;
}
FlashLoadChunk(hdr->sector * SAVE_SECTOR_SIZE, ret, size);
return ret;
}
if (valid1 == FALSE && valid2 == TRUE && sp24 == sp28) {
if (sp1C == 0) {
sub_020286D4(saveData, idx, sp20, sp20, 1);
*ret2_p = 1;
}
FlashLoadChunk((hdr->sector + 64) * SAVE_SECTOR_SIZE, ret, size);
return ret;
}
if (valid1 == TRUE && valid2 == TRUE) {
if (sp1C == 0) {
if (sp24 == sp2C) {
FlashLoadChunk(hdr->sector * SAVE_SECTOR_SIZE, ret, size);
return ret;
} else if (sp20 == sp28) {
sub_020286D4(saveData, idx, sp20, sp20, sp1C ^ 1);
*ret2_p = 1;
FlashLoadChunk((hdr->sector + 64) * SAVE_SECTOR_SIZE, ret, size);
return ret;
}
} else {
if (sp24 == sp28) {
FlashLoadChunk((hdr->sector + 64) * SAVE_SECTOR_SIZE, ret, size);
return ret;
} else if (sp20 == sp2C) {
sub_020286D4(saveData, idx, sp20, sp20, sp1C ^ 1);
*ret2_p = 1;
FlashLoadChunk(hdr->sector * SAVE_SECTOR_SIZE, ret, size);
return ret;
}
}
}
*ret_p = 2;
sub_0202AC60(misc, hdr->id, -1, -1, 0);
return ret;
}
static void sub_020286B4(SaveData *saveData, int a1, u32 *a2, u32 *a3, u8 *a4) {
sub_0202AC38(Save_Misc_Get(saveData), a1, a2, a3, a4);
}
static void sub_020286D4(SaveData *saveData, int a1, u32 a2, u32 a3, u8 a4) {
sub_0202AC60(Save_Misc_Get(saveData), a1, a2, a3, a4);
}
static BOOL SaveDetectFlash(void) {
s32 lockId;
CARDBackupType flash_id;
lockId = OS_GetLockID();
GF_ASSERT(lockId != OS_LOCK_ID_ERROR);
CARD_LockBackup(lockId);
if (CARD_IdentifyBackup(CARD_BACKUP_TYPE_FLASH_4MBITS_EX)) {
flash_id = CARD_BACKUP_TYPE_FLASH_4MBITS_EX;
} else if (CARD_IdentifyBackup(CARD_BACKUP_TYPE_FLASH_4MBITS)) {
flash_id = CARD_BACKUP_TYPE_FLASH_4MBITS;
} else {
flash_id = CARD_BACKUP_TYPE_NOT_USE;
}
CARD_UnlockBackup(lockId);
OS_ReleaseLockID(lockId);
return flash_id != CARD_BACKUP_TYPE_NOT_USE;
}
static s32 FlashWriteChunk(u32 offset, void *data, u32 size) {
int stat;
s32 lockId;
lockId = FlashWriteChunkInternal(offset, data, size);
while (!WaitFlashWrite(lockId, 1, &stat)) {}
return stat;
}
static BOOL FlashLoadChunk(u32 offset, void *data, u32 size) {
u32 lock;
BOOL result;
lock = OS_GetLockID();
GF_ASSERT(lock != OS_LOCK_ID_ERROR);
CARD_LockBackup(lock);
CARD_ReadBackupAsync(offset, data, size, NULL, NULL);
result = CARD_WaitBackupAsync();
CARD_UnlockBackup(lock);
OS_ReleaseLockID(lock);
if (!result) {
FreeToHeap(sSaveDataPtr);
ShowSaveDataReadError(HEAP_ID_1);
}
return result;
}
static void FlashWriteCommandCallback(void *arg) {
#pragma unused(arg)
saveWritten = TRUE;
}
static s32 FlashWriteChunkInternal(u32 offset, void *data, u32 size) {
s32 lock;
u32 sp14;
lock = OS_GetLockID();
GF_ASSERT(lock != OS_LOCK_ID_ERROR);
CARD_LockBackup(lock);
if (!CARD_ReadBackup(0, &sp14, sizeof(u32))) {
SaveErrorHandling(lock, 1);
}
saveWritten = FALSE;
CARD_WriteAndVerifyBackupAsync(offset, data, size, FlashWriteCommandCallback, NULL);
return lock;
}
static BOOL WaitFlashWrite(s32 lockId, BOOL checkResult, BOOL *resultSuccess) {
if (saveWritten == TRUE) {
if (!checkResult) {
return TRUE;
}
switch (CARD_GetResultCode()) {
case CARD_RESULT_SUCCESS:
*resultSuccess = TRUE;
break;
case CARD_RESULT_TIMEOUT:
default:
*resultSuccess = FALSE;
SaveErrorHandling(lockId, 0);
case CARD_RESULT_NO_RESPONSE:
*resultSuccess = FALSE;
SaveErrorHandling(lockId, 1);
}
CARD_UnlockBackup(lockId);
OS_ReleaseLockID(lockId);
return TRUE;
}
return FALSE;
}
static void SaveErrorHandling(s32 lockId, int code) {
CARD_UnlockBackup(lockId);
OS_ReleaseLockID(lockId);
FreeToHeap(sSaveDataPtr);
ShowSaveDataWriteError(HEAP_ID_1, code);
}
BOOL SaveSubstruct_AssertCRC(int idx) {
u8 *data;
int size;
u16 *data_u16;
u16 crc;
data = SaveArray_Get(SaveData_Get(), idx);
data_u16 = (u16 *)data;
size = GetSaveChunkSizePlusCRC(idx) - 4;
crc = GF_CalcCRC16(data, size);
if (crc == data_u16[size / 2]) {
return TRUE;
}
GF_ASSERT(0);
return FALSE;
}
void SaveSubstruct_UpdateCRC(int idx) {
u8 *data;
int size;
u16 *data_u16;
u16 crc;
data = SaveArray_Get(SaveData_Get(), idx);
data_u16 = (u16 *)data;
size = GetSaveChunkSizePlusCRC(idx) - 4;
crc = GF_CalcCRC16(data, size);
data_u16[size / 2] = crc;
}
static int HandleWriteSaveAsync_PCBoxes(SaveData *saveData, struct AsyncWriteManager *writeMan) {
u32 r7;
int r0;
int sp0;
void *data;
switch (writeMan->state) {
case 0:
saveData->newBoxModifiedFlags = Save_CalcPCBoxModifiedFlags(saveData);
saveData->numModifiedBoxes = PCModifiedFlags_CountModifiedBoxes(saveData->newBoxModifiedFlags);
saveData->nextBoxToWrite = 0;
r7 = PCStorage_GetSizeOfBox() * PCStorage_GetNumBoxes();
saveData->pcStorageCRC = GF_CalcCRC16(SaveArray_Get(saveData, SAVE_PCSTORAGE), r7);
if (saveData->numModifiedBoxes == 0) {
GF_ASSERT(saveData->pcStorageCRC == saveData->pcStorageLastCRC);
sub_020271A0(saveData);
if (saveData->pcStorageCRC != saveData->pcStorageLastCRC) {
saveData->numModifiedBoxes = PCStorage_GetNumBoxes();
saveData->newBoxModifiedFlags = BOX_ALL_MODIFIED_FLAG;
Save_SetAllPCBoxesModified(saveData);
}
}
writeMan->state_sub = 0;
data = saveData->dynamic_region;
SaveSlot_BuildFooter(saveData, data, writeMan->curSector);
GetSaveSectorFooterPtr(saveData, data, writeMan->curSector);
writeMan->state++;
// fallthrough
case 1:
r0 = Save_WritePCBoxes(saveData, writeMan);
if (r0 == 0) {
return WRITE_STATUS_TOTAL_FAIL;
}
if (r0 == 1) {
writeMan->state++;
if (writeMan->curSector + 1 == writeMan->numSectors) {
return WRITE_STATUS_NEXT;
}
}
break;
case 2:
writeMan->lockId = Save_WriteChunkFooterAsync(saveData, writeMan->curSector, saveData->lastGoodSector == 0);
writeMan->waitingAsync = 1;
writeMan->state++;
// fallthrough
case 3:
if (WaitFlashWrite(writeMan->lockId, writeMan->waitingAsync, &sp0)) {
writeMan->waitingAsync = 0;
if (!sp0) {
return WRITE_STATUS_TOTAL_FAIL;
}
writeMan->curSector++;
if (writeMan->curSector == writeMan->numSectors) {
return WRITE_STATUS_SUCCESS;
}
writeMan->state = 0;
}
break;
}
return WRITE_STATUS_CONTINUE;
}
static int Save_WritePCBoxes(SaveData *saveData, struct AsyncWriteManager *writeMan) {
int write_ok;
switch (writeMan->state_sub) {
case 0:
if (saveData->nextBoxToWrite >= saveData->numModifiedBoxes) {
writeMan->state_sub = 3;
} else {
writeMan->state_sub++;
}
break;
case 1:
writeMan->lockId = Save_WriteNextPCBox(saveData, &saveData->saveSlotSpecs[writeMan->curSector], saveData->lastGoodSector == 0);
writeMan->waitingAsync = 1;
writeMan->state_sub++;
// fallthrough
case 2:
if (WaitFlashWrite(writeMan->lockId, writeMan->waitingAsync, &write_ok)) {
writeMan->waitingAsync = 0;
if (!write_ok) {
return 0;
}
saveData->nextBoxToWrite++;
writeMan->state_sub = 0;
}
break;
case 3:
writeMan->lockId = Save_WritePCFooter(saveData, &saveData->saveSlotSpecs[writeMan->curSector], saveData->lastGoodSector == 0);
writeMan->waitingAsync = 1;
writeMan->state_sub++;
// fallthrough
case 4:
if (WaitFlashWrite(writeMan->lockId, writeMan->waitingAsync, &write_ok)) {
writeMan->waitingAsync = 0;
writeMan->state_sub = 0;
if (!write_ok) {
return 0;
}
return 1;
}
break;
}
return 2;
}
static int Save_WriteNextPCBox(SaveData *saveData, struct SaveSlotSpec *spec, u8 slot) {
u32 boxno;
u32 box_size;
u32 offset;
box_size = PCStorage_GetSizeOfBox();
offset = GetChunkOffsetFromCurrentSaveSlot(slot, spec);
boxno = PCModifiedFlags_GetIndexOfNthModifiedBox(saveData->newBoxModifiedFlags, saveData->nextBoxToWrite);
GF_ASSERT(boxno != 0xFF);
return FlashWriteChunkInternal(offset + box_size * boxno, saveData->dynamic_region + spec->offset + box_size * boxno, box_size);
}
static int Save_WritePCFooter(SaveData *saveData, struct SaveSlotSpec *spec, u8 slot) {
u32 sector_size;
struct SaveChunkFooter *footer;
u32 spec_offset;
void *dynamic_region_ptr;
u32 offset;
void *data;
u32 pc_size;
u16 crc;
pc_size = PCStorage_GetSizeOfBox() * PCStorage_GetNumBoxes();
offset = GetChunkOffsetFromCurrentSaveSlot(slot, spec);
data = saveData->dynamic_region + spec->offset;
sector_size = spec->size - sizeof(struct SaveChunkFooter);
GF_ASSERT(sector_size != 0);
dynamic_region_ptr = saveData->dynamic_region;
spec_offset = spec->offset;
footer = GetSaveSectorFooterPtr(saveData, dynamic_region_ptr, 1);
crc = SaveArray_CalcCRC16MinusFooter(saveData, (u8 *)dynamic_region_ptr + spec_offset, spec->size);
GF_ASSERT(crc == footer->crc);
return FlashWriteChunkInternal(offset + pc_size, data + pc_size, sector_size - pc_size);
}
static u32 Save_CalcPCBoxModifiedFlags(SaveData *saveData) {
u32 ret;
ret = Save_GetPCBoxModifiedFlags(saveData);
ret |= saveData->boxModifiedFlags;
if (saveData->sectorCleanFlag[0] || saveData->sectorCleanFlag[1]) {
ret = BOX_ALL_MODIFIED_FLAG;
}
return ret;
}
static u32 PCModifiedFlags_CountModifiedBoxes(u32 flags) {
u8 i, n;
u32 t;
n = 0;
t = PCStorage_GetNumBoxes();
for (i = 0; i < t; i++) {
if (flags & 1) {
n++;
}
flags >>= 1;
flags &= BOX_ALL_MODIFIED_FLAG;
}
return n;
}
static u32 PCModifiedFlags_GetIndexOfNthModifiedBox(u32 flags, u8 last) {
u8 i, n;
u32 t;
n = 0;
t = PCStorage_GetNumBoxes();
for (i = 0; i < t; i++) {
if (flags & 1) {
if (n == last) {
return i;
}
n++;
}
flags >>= 1;
flags &= BOX_ALL_MODIFIED_FLAG;
}
return 0xFF;
}