GodMode9/source/nand/agbsave.c
2017-09-13 16:36:31 +02:00

93 lines
3.3 KiB
C

#include "agbsave.h"
#include "sha.h"
#include "aes.h"
u32 ValidateAgbSaveHeader(AgbSaveHeader* header) {
u8 magic[] = { AGBSAVE_MAGIC };
// basic checks
if ((memcmp(header->magic, magic, sizeof(magic)) != 0) ||
(header->unknown0 != 1) || (header->save_start != 0x200) ||
(header->save_size > AGBSAVE_MAX_SSIZE))
return 1;
// reserved area checks
for (u32 i = 0; i < sizeof(header->reserved0); i++) if (header->reserved0[i] != 0xFF) return 1;
for (u32 i = 0; i < sizeof(header->reserved1); i++) if (header->reserved1[i] != 0xFF) return 1;
for (u32 i = 0; i < sizeof(header->reserved2); i++) if (header->reserved2[i] != 0xFF) return 1;
for (u32 i = 0; i < sizeof(header->reserved3); i++) if (header->reserved3[i] != 0xFF) return 1;
// all fine if arriving here
return 0;
}
u32 LoadAgbSave(u32 nand_src, u8* agbsave, u32 max_size, NandPartitionInfo* info, bool header_only) {
AgbSaveHeader* header = (AgbSaveHeader*) agbsave;
// need at least room for the header
if (max_size < sizeof(AgbSaveHeader)) return 1;
// load the header
if ((GetNandPartitionInfo(info, NP_TYPE_AGB, NP_SUBTYPE_CTR, 0, nand_src) != 0) ||
(ReadNandSectors(agbsave, info->sector, 1, info->keyslot, nand_src) != 0) ||
(ValidateAgbSaveHeader(header) != 0) ||
(sizeof(AgbSaveHeader) + header->save_size > info->count * 0x200))
return 1;
// done if we only want the header
if (header_only) return 0;
// load the savegame
if ((sizeof(AgbSaveHeader) + header->save_size > max_size) ||
(ReadNandBytes(agbsave + 0x200, (info->sector+1) * 0x200, header->save_size, info->keyslot, nand_src) != 0))
return 1;
return 0;
}
u32 GetAgbSaveSize(u32 nand_src) {
AgbSaveHeader* header = (AgbSaveHeader*) NAND_BUFFER;
NandPartitionInfo info;
if (LoadAgbSave(nand_src, NAND_BUFFER, NAND_BUFFER_SIZE, &info, true) != 0)
return 1;
return header->save_size; // it's recommended to also check the CMAC
}
u32 CheckAgbSaveCmac(u32 nand_src) {
u8* agbsave = (u8*) NAND_BUFFER;
AgbSaveHeader* header = (AgbSaveHeader*) agbsave;
NandPartitionInfo info;
if (LoadAgbSave(nand_src, agbsave, NAND_BUFFER_SIZE, &info, false) != 0)
return 1;
u8 cmac[16] __attribute__((aligned(32)));
u8 shasum[32];
sha_quick(shasum, agbsave + 0x30, (0x200 - 0x30) + header->save_size, SHA256_MODE);
use_aeskey(0x24);
aes_cmac(shasum, cmac, 2);
return (memcmp(cmac, header->cmac, 16) == 0) ? 0 : 1;
}
u32 FixAgbSaveCmac(u32 nand_dst) {
u8* agbsave = (u8*) NAND_BUFFER;
AgbSaveHeader* header = (AgbSaveHeader*) agbsave;
NandPartitionInfo info;
if (LoadAgbSave(nand_dst, agbsave, NAND_BUFFER_SIZE, &info, false) != 0)
return 1;
u8 cmac[16] __attribute__((aligned(32)));
u8 shasum[32];
sha_quick(shasum, agbsave + 0x30, (0x200 - 0x30) + header->save_size, SHA256_MODE);
use_aeskey(0x24);
aes_cmac(shasum, cmac, 2);
memcpy(header->cmac, cmac, 16);
// set CFG_BOOTENV to 0x7 so the save is taken over
// https://www.3dbrew.org/wiki/CONFIG_Registers#CFG_BOOTENV
*(u32*) 0x10010000 = 0x7;
return (WriteNandSectors(header, info.sector, 1, info.keyslot, nand_dst) == 0) ? 0 : 1;
}