diff --git a/source/filesys/filetype.c b/source/filesys/filetype.c index aa9c2d0..bada2d8 100644 --- a/source/filesys/filetype.c +++ b/source/filesys/filetype.c @@ -3,6 +3,7 @@ #include "fatmbr.h" #include "nand.h" #include "game.h" +#include "agbsave.h" #include "keydb.h" #include "ctrtransfer.h" #include "scripting.h" @@ -64,6 +65,8 @@ u32 IdentifyFileType(const char* path) { return GAME_TICKET; // Ticket file (not used for anything right now) } else if (ValidateFirmHeader((FirmHeader*) data, fsize) == 0) { return SYS_FIRM; // FIRM file + } else if ((ValidateAgbSaveHeader((AgbSaveHeader*) data) == 0) && (fsize >= AGBSAVE_MAX_SIZE)) { + return SYS_AGBSAVE; // AGBSAVE file } else if (memcmp(header + 0x100, tickdb_magic, sizeof(tickdb_magic)) == 0) { return SYS_TICKDB; // ticket.db } else if (memcmp(header, smdh_magic, sizeof(smdh_magic)) == 0) { diff --git a/source/filesys/filetype.h b/source/filesys/filetype.h index 22d09f8..058c53c 100644 --- a/source/filesys/filetype.h +++ b/source/filesys/filetype.h @@ -16,15 +16,16 @@ #define GAME_SMDH (1UL<<11) #define GAME_NDS (1UL<<12) #define SYS_FIRM (1UL<<13) -#define SYS_TICKDB (1UL<<14) -#define BIN_NCCHNFO (1UL<<15) -#define BIN_TIKDB (1UL<<16) -#define BIN_KEYDB (1UL<<17) -#define BIN_LEGKEY (1UL<<18) -#define TXT_SCRIPT (1UL<<19) -#define TXT_GENERIC (1UL<<20) -#define NOIMG_NAND (1UL<<21) -#define HDR_NAND (1UL<<22) +#define SYS_AGBSAVE (1UL<<14) +#define SYS_TICKDB (1UL<<15) +#define BIN_NCCHNFO (1UL<<16) +#define BIN_TIKDB (1UL<<17) +#define BIN_KEYDB (1UL<<18) +#define BIN_LEGKEY (1UL<<19) +#define TXT_SCRIPT (1UL<<20) +#define TXT_GENERIC (1UL<<21) +#define NOIMG_NAND (1UL<<22) +#define HDR_NAND (1UL<<23) #define TYPE_BASE 0x00FFFFFF // 24 bit reserved for base types #define FLAG_ENC (1UL<<28) @@ -53,5 +54,6 @@ #define FTYPE_SCRIPT(tp) (tp&(TXT_SCRIPT)) #define FTYPE_BOOTABLE(tp) (tp&(SYS_FIRM)) #define FTYPE_INSTALLABLE(tp) (tp&(SYS_FIRM)) +#define FTPYE_AGBSAVE(tp) (tp&(SYS_AGBSAVE)) u32 IdentifyFileType(const char* path); diff --git a/source/godmode.c b/source/godmode.c index e5fed83..56c5217 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -837,7 +837,9 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur bool scriptable = (FTYPE_SCRIPT(filetype)); bool bootable = (FTYPE_BOOTABLE(filetype) && !(drvtype & DRV_VIRTUAL)); bool installable = (FTYPE_INSTALLABLE(filetype) && !(drvtype & DRV_VIRTUAL)); - bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || transferable || hsinjectable || restorable || xorpadable || ebackupable || ncsdfixable || extrcodeable || keyinitable || bootable || scriptable || installable; + bool agbexportable = (FTPYE_AGBSAVE(filetype)); + bool agbimportable = (FTPYE_AGBSAVE(filetype) && (drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND)); + bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || transferable || hsinjectable || restorable || xorpadable || ebackupable || ncsdfixable || extrcodeable || keyinitable || bootable || scriptable || installable || agbexportable || agbimportable; char pathstr[32+1]; TruncateString(pathstr, curr_entry->path, 32, 8); @@ -878,6 +880,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (filetype & GAME_NDS) ? "NDS image options..." : (filetype & GAME_TICKET)? "Ticket options..." : (filetype & SYS_FIRM ) ? "FIRM image options..." : + (filetype & SYS_AGBSAVE)? (agbimportable) ? "AGBSAVE options..." : "Dump GBA VC save" : (filetype & SYS_TICKDB) ? (tik_buildable) ? "Ticket.db options..." : "Mount as ticket.db" : (filetype & BIN_TIKDB) ? "Titlekey options..." : (filetype & BIN_KEYDB) ? "AESkeydb options..." : @@ -901,14 +904,17 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur FileHexViewer(curr_entry->path); GetDirContents(current_dir, current_path); return 0; - } else if (user_select == textviewer) { // -> show in text viewer + } + else if (user_select == textviewer) { // -> show in text viewer FileTextViewer(curr_entry->path, scriptable); return 0; - } else if (user_select == calcsha) { // -> calculate SHA-256 + } + else if (user_select == calcsha) { // -> calculate SHA-256 Sha256Calculator(curr_entry->path); GetDirContents(current_dir, current_path); return 0; - } else if (user_select == calccmac) { // -> calculate CMAC + } + else if (user_select == calccmac) { // -> calculate CMAC optionstr[0] = "Check current CMAC only"; optionstr[1] = "Verify CMAC for all"; optionstr[2] = "Fix CMAC for all"; @@ -951,7 +957,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur return 0; } return FileHandlerMenu(current_path, cursor, scroll, current_dir, clipboard); - } else if (user_select == fileinfo) { // -> show file info + } + else if (user_select == fileinfo) { // -> show file info FILINFO fno; if (fvx_stat(curr_entry->path, &fno) != FR_OK) { ShowPrompt(false, "%s\nFile info failed!", pathstr); @@ -968,10 +975,12 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (fno.fattrib & AM_RDO) ? 'X' : ' ', (fno.fattrib & AM_HID) ? 'X' : ' ', (fno.fattrib & AM_SYS) ? 'X' : ' ' , (fno.fattrib & AM_ARC) ? 'X' : ' ', (fno.fattrib & AM_VRT) ? 'X' : ' '); return 0; - } else if (user_select == copystd) { // -> copy to OUTPUT_PATH + } + else if (user_select == copystd) { // -> copy to OUTPUT_PATH StandardCopy(cursor, scroll, current_dir); return 0; - } else if (user_select == inject) { // -> inject data from clipboard + } + else if (user_select == inject) { // -> inject data from clipboard char origstr[18 + 1]; TruncateString(origstr, clipboard->entry[0].name, 18, 10); u64 offset = ShowHexPrompt(0, 8, "Inject data from %s?\nSpecifiy offset below.", origstr); @@ -981,7 +990,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur clipboard->n_entries = 0; } return 0; - } else if (user_select == searchdrv) { // -> search drive, open containing path + } + else if (user_select == searchdrv) { // -> search drive, open containing path char* last_slash = strrchr(curr_entry->path, '/'); if (last_slash) { snprintf(current_path, last_slash - curr_entry->path + 1, "%s", curr_entry->path); @@ -990,7 +1000,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur *scroll = 0; } return 0; - } else if (user_select != special) { + } + else if (user_select != special) { return 1; } @@ -1020,6 +1031,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur int install = (installable) ? ++n_opt : -1; int boot = (bootable) ? ++n_opt : -1; int script = (scriptable) ? ++n_opt : -1; + int agbexport = (agbexportable) ? ++n_opt : -1; + int agbimport = (agbimportable) ? ++n_opt : -1; if (mount > 0) optionstr[mount-1] = "Mount image to drive"; if (restore > 0) optionstr[restore-1] = "Restore SysNAND (safe)"; if (ebackup > 0) optionstr[ebackup-1] = "Update embedded backup"; @@ -1044,6 +1057,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur if (install > 0) optionstr[install-1] = "Install FIRM"; if (boot > 0) optionstr[boot-1] = "Boot FIRM"; if (script > 0) optionstr[script-1] = "Execute GM9 script"; + if (agbexport > 0) optionstr[agbexport-1] = "Dump GBA VC save"; + if (agbimport > 0) optionstr[agbimport-1] = "Inject GBA VC save"; // auto select when there is only one option user_select = (n_opt <= 1) ? n_opt : (int) ShowSelectPrompt(n_opt, optionstr, (n_marked > 1) ? @@ -1070,7 +1085,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur } } return 0; - } else if (user_select == decrypt) { // -> decrypt game file + } + else if (user_select == decrypt) { // -> decrypt game file if (cryptable_inplace) { optionstr[0] = "Decrypt to " OUTPUT_PATH; optionstr[1] = "Decrypt inplace"; @@ -1122,7 +1138,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur } } return 0; - } else if (user_select == encrypt) { // -> encrypt game file + } + else if (user_select == encrypt) { // -> encrypt game file if (cryptable_inplace) { optionstr[0] = "Encrypt to " OUTPUT_PATH; optionstr[1] = "Encrypt inplace"; @@ -1165,7 +1182,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur else ShowPrompt(false, "%s\nEncrypted to %s", pathstr, OUTPUT_PATH); } return 0; - } else if ((user_select == cia_build) || (user_select == cia_build_legit) || (user_select == cxi_dump)) { // -> build CIA / dump CXI + } + else if ((user_select == cia_build) || (user_select == cia_build_legit) || (user_select == cxi_dump)) { // -> build CIA / dump CXI char* type = (user_select == cxi_dump) ? "CXI" : "CIA"; bool force_legit = (user_select == cia_build_legit); if ((n_marked > 1) && ShowPrompt(true, "Try to process all %lu selected files?", n_marked)) { @@ -1202,7 +1220,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur } else ShowPrompt(false, "%s\n%s build failed", pathstr, type); } return 0; - } else if (user_select == verify) { // -> verify game / nand file + } + else if (user_select == verify) { // -> verify game / nand file if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) { u32 n_success = 0; u32 n_other = 0; @@ -1239,7 +1258,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (VerifyGameFile(curr_entry->path) == 0) ? "success" : "failed"); } return 0; - } else if ((user_select == tik_build_enc) || (user_select == tik_build_dec)) { // -> (Re)Build titlekey database + } + else if ((user_select == tik_build_enc) || (user_select == tik_build_dec)) { // -> (Re)Build titlekey database bool dec = (user_select == tik_build_dec); const char* path_out = (dec) ? OUTPUT_PATH "/" TIKDB_NAME_DEC : OUTPUT_PATH "/" TIKDB_NAME_ENC; if (BuildTitleKeyInfo(NULL, dec, false) != 0) return 1; // init database @@ -1266,7 +1286,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur } else ShowPrompt(false, "%s\nBuild database %s.", path_out, (BuildTitleKeyInfo(curr_entry->path, dec, true) == 0) ? "success" : "failed"); return 0; - } else if (user_select == key_build) { // -> (Re)Build AES key database + } + else if (user_select == key_build) { // -> (Re)Build AES key database const char* path_out = OUTPUT_PATH "/" KEYDB_NAME; if (BuildKeyDb(NULL, false) != 0) return 1; // init database ShowString("Building %s...", KEYDB_NAME); @@ -1292,7 +1313,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur } else ShowPrompt(false, "%s\nBuild database %s.", path_out, (BuildKeyDb(curr_entry->path, true) == 0) ? "success" : "failed"); return 0; - } else if (user_select == rename) { // -> Game file renamer + } + else if (user_select == rename) { // -> Game file renamer if ((n_marked > 1) && ShowPrompt(true, "Try to rename all %lu selected files?", n_marked)) { u32 n_success = 0; ShowProgress(0, 0, ""); @@ -1309,11 +1331,13 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur ShowPrompt(false, "%s\nCould not rename\n(Maybe try decrypt?)", pathstr); } return 0; - } else if (user_select == show_info) { // -> Show title info + } + else if (user_select == show_info) { // -> Show title info if (ShowGameFileTitleInfo(curr_entry->path) != 0) ShowPrompt(false, "Title info: not found"); return 0; - } else if (user_select == hsinject) { // -> Inject to Health & Safety + } + else if (user_select == hsinject) { // -> Inject to Health & Safety char* destdrv[2] = { NULL }; n_opt = 0; if (DriveType("1:")) { @@ -1330,13 +1354,15 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (InjectHealthAndSafety(curr_entry->path, destdrv[user_select-1]) == 0) ? "success" : "failed"); } return 0; - } else if (user_select == extrcode) { // -> Extract code + } + else if (user_select == extrcode) { // -> Extract code ShowString("%s\nExtracting .code, please wait...", pathstr); if (ExtractCodeFromCxiFile(curr_entry->path, NULL) == 0) { ShowPrompt(false, "%s\n.code extracted to " OUTPUT_PATH, pathstr); } else ShowPrompt(false, "%s\n.code extract failed", pathstr); return 0; - } else if (user_select == ctrtransfer) { // -> transfer CTRNAND image to SysNAND + } + else if (user_select == ctrtransfer) { // -> transfer CTRNAND image to SysNAND char* destdrv[2] = { NULL }; n_opt = 0; if (DriveType("1:")) { @@ -1355,17 +1381,20 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur } } else ShowPrompt(false, "%s\nNo valid destination found"); return 0; - } else if (user_select == restore) { // -> restore SysNAND (A9LH preserving) + } + else if (user_select == restore) { // -> restore SysNAND (A9LH preserving) ShowPrompt(false, "%s\nNAND restore %s", pathstr, (SafeRestoreNandDump(curr_entry->path) == 0) ? "success" : "failed"); return 0; - } else if (user_select == ncsdfix) { // -> inject sighaxed NCSD + } + else if (user_select == ncsdfix) { // -> inject sighaxed NCSD ShowPrompt(false, "%s\nRebuild NCSD %s", pathstr, (FixNandHeader(curr_entry->path, !(filetype == HDR_NAND)) == 0) ? "success" : "failed"); GetDirContents(current_dir, current_path); InitExtFS(); // this might have fixed something, so try this return 0; - } else if ((user_select == xorpad) || (user_select == xorpad_inplace)) { // -> build xorpads + } + else if ((user_select == xorpad) || (user_select == xorpad_inplace)) { // -> build xorpads bool inplace = (user_select == xorpad_inplace); bool success = (BuildNcchInfoXorpads((inplace) ? current_path : OUTPUT_PATH, curr_entry->path) == 0); ShowPrompt(false, "%s\nNCCHinfo padgen %s%s", pathstr, @@ -1381,7 +1410,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur *cursor = 1; } return 0; - } else if (user_select == ebackup) { // -> update embedded backup + } + else if (user_select == ebackup) { // -> update embedded backup ShowString("%s\nUpdating embedded backup...", pathstr); bool required = (CheckEmbeddedBackup(curr_entry->path) != 0); bool success = (required && (EmbedEssentialBackup(curr_entry->path) == 0)); @@ -1389,11 +1419,13 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (success) ? "completed" : "failed!"); GetDirContents(current_dir, current_path); return 0; - } else if ((user_select == keyinit)) { // -> initialise keys from aeskeydb.bin + } + else if (user_select == keyinit) { // -> initialise keys from aeskeydb.bin if (ShowPrompt(true, "Warning: Keys are not verified.\nContinue on your own risk?")) ShowPrompt(false, "%s\nAESkeydb init %s", pathstr, (InitKeyDb(curr_entry->path) == 0) ? "success" : "failed"); return 0; - } else if ((user_select == install)) { // -> install FIRM + } + else if (user_select == install) { // -> install FIRM size_t firm_size = FileGetSize(curr_entry->path); u32 slots = 1; if (GetNandPartitionInfo(NULL, NP_TYPE_FIRM, NP_SUBTYPE_CTR, 1, NAND_SYSNAND) == 0) { @@ -1406,16 +1438,31 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur if (slots) ShowPrompt(false, "%s (%dkB)\nInstall %s", pathstr, firm_size / 1024, (SafeInstallFirm(curr_entry->path, slots) == 0) ? "success" : "failed!"); return 0; - } else if ((user_select == boot)) { // -> boot FIRM + } + else if (user_select == boot) { // -> boot FIRM BootFirmHandler(curr_entry->path, true, false); return 0; - } else if ((user_select == script)) { // execute script + } + else if (user_select == script) { // execute script if (ShowPrompt(true, "%s\nWarning: Do not run scripts\nfrom untrusted sources.\n \nExecute script?", pathstr)) ShowPrompt(false, "%s\nScript execute %s", pathstr, ExecuteGM9Script(curr_entry->path) ? "success" : "failure"); GetDirContents(current_dir, current_path); ClearScreenF(true, true, COLOR_STD_BG); return 0; } + else if (user_select == agbexport) { // export GBA VC save + if (DumpGbaVcSavegame(curr_entry->path) == 0) + ShowPrompt(false, "Savegame dumped to " OUTPUT_PATH); + else ShowPrompt(false, "Savegame dump failed!"); + return 0; + } + else if (user_select == agbimport) { // import GBA VC save + if (clipboard->n_entries != 1) { + ShowPrompt(false, "GBA VC savegame has to\nbe in the clipboard."); + } else ShowPrompt(false, "Savegame inject %s", + (InjectGbaVcSavegame(curr_entry->path, clipboard->entry[0].path) == 0) ? "success" : "failed!"); + return 0; + } return FileHandlerMenu(current_path, cursor, scroll, current_dir, clipboard); } @@ -1459,14 +1506,16 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar InitExtFS(); GetDirContents(current_dir, current_path); return 0; - } else if (user_select == bonus) { // setup bonus drive + } + else if (user_select == bonus) { // setup bonus drive if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & (DRV_BONUS|DRV_IMAGE))) clipboard->n_entries = 0; // remove bonus drive clipboard entries if (!SetupBonusDrive()) ShowPrompt(false, "Setup failed!"); ClearScreenF(true, true, COLOR_STD_BG); GetDirContents(current_dir, current_path); return 0; - } else if (user_select == multi) { // switch EmuNAND offset + } + else if (user_select == multi) { // switch EmuNAND offset while (ShowPrompt(true, "Current EmuNAND offset is %06X.\nSwitch to next offset?", GetEmuNandBase())) { if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & DRV_EMUNAND)) clipboard->n_entries = 0; // remove EmuNAND clipboard entries @@ -1476,7 +1525,8 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar } GetDirContents(current_dir, current_path); return 0; - } else if (user_select == bsupport) { // build support files + } + else if (user_select == bsupport) { // build support files bool tik_enc_sys = false; bool tik_enc_emu = false; if (BuildTitleKeyInfo(NULL, false, false) == 0) { @@ -1510,7 +1560,8 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar SEEDDB_NAME, seed_sys ? seed_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed"); GetDirContents(current_dir, current_path); return 0; - } else if (user_select == hsrestore) { // restore Health & Safety + } + else if (user_select == hsrestore) { // restore Health & Safety n_opt = 0; int sys = (CheckHealthAndSafetyInject("1:") == 0) ? (int) ++n_opt : -1; int emu = (CheckHealthAndSafetyInject("4:") == 0) ? (int) ++n_opt : -1; @@ -1522,7 +1573,8 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar GetDirContents(current_dir, current_path); return 0; } - } else if (user_select == clock) { // RTC clock setter + } + else if (user_select == clock) { // RTC clock setter DsTime dstime; get_dstime(&dstime); if (ShowRtcSetterPrompt(&dstime, "Set RTC date&time:")) { @@ -1533,7 +1585,8 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar timestr); } return 0; - } else if (user_select == sysinfo) { // Myria's system info + } + else if (user_select == sysinfo) { // Myria's system info char* sysinfo_txt = (char*) TEMP_BUFFER; MyriaSysinfo(sysinfo_txt); MemTextViewer(sysinfo_txt, strnlen(sysinfo_txt, TEMP_BUFFER_SIZE), false); diff --git a/source/nand/agbsave.c b/source/nand/agbsave.c index c299886..185a4d4 100644 --- a/source/nand/agbsave.c +++ b/source/nand/agbsave.c @@ -9,7 +9,7 @@ u32 ValidateAgbSaveHeader(AgbSaveHeader* header) { // basic checks if ((memcmp(header->magic, magic, sizeof(magic)) != 0) || (header->unknown0 != 1) || (header->save_start != 0x200) || - (header->save_size > AGBSAVE_MAX_SSIZE)) + (header->save_size > AGBSAVE_MAX_SSIZE) || !(GBASAVE_VALID(header->save_size))) return 1; // reserved area checks @@ -21,72 +21,3 @@ u32 ValidateAgbSaveHeader(AgbSaveHeader* header) { // 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; -} diff --git a/source/nand/agbsave.h b/source/nand/agbsave.h index 2f4c83f..1ae2d6a 100644 --- a/source/nand/agbsave.h +++ b/source/nand/agbsave.h @@ -7,6 +7,19 @@ #define AGBSAVE_MAX_SIZE (0x000180 * 0x200) // standard size of the NAND partition #define AGBSAVE_MAX_SSIZE (AGBSAVE_MAX_SIZE - sizeof(AgbSaveHeader)) +// see: http://3dbrew.org/wiki/3DS_Virtual_Console#GBA_VC +#define GBASAVE_EEPROM_512 (512) +#define GBASAVE_EEPROM_8K (8 * 1024) +#define GBASAVE_SRAM_32K (32 * 1024) +#define GBASAVE_FLASH_64K (64 * 1024) +#define GBASAVE_FLASH_128K (128 * 1024) +#define GBASAVE_VALID(size) \ + (((size) == GBASAVE_EEPROM_512) || \ + ((size) == GBASAVE_EEPROM_8K) || \ + ((size) == GBASAVE_SRAM_32K) || \ + ((size) == GBASAVE_FLASH_64K) || \ + ((size) == GBASAVE_FLASH_128K)) + // see: http://3dbrew.org/wiki/3DS_Virtual_Console#NAND_Savegame typedef struct { u8 magic[4]; // ".SAV" @@ -26,6 +39,3 @@ typedef struct { } __attribute__((packed)) AgbSaveHeader; u32 ValidateAgbSaveHeader(AgbSaveHeader* header); -u32 GetAgbSaveSize(u32 nand_src); -u32 CheckAgbSaveCmac(u32 nand_src); -u32 FixAgbSaveCmac(u32 nand_dst); diff --git a/source/nand/nand.c b/source/nand/nand.c index 8b4df47..f36cff8 100644 --- a/source/nand/nand.c +++ b/source/nand/nand.c @@ -534,19 +534,6 @@ u32 GetNandPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 ind return 0; } -// OTP hash is 32 byte in size -u32 GetOtpHash(void* hash) { - if (!CheckSector0x96Crypto()) return 1; - memcpy(hash, OtpSha256, 0x20); - return 0; -} - -// NAND CID is 16 byte in size -u32 GetNandCid(void* cid) { - sdmmc_get_cid(1, (u32*) cid); - return 0; -} - bool CheckMultiEmuNand(void) { // this only checks for the theoretical possibility diff --git a/source/nand/nand.h b/source/nand/nand.h index b8d818d..7db6015 100644 --- a/source/nand/nand.h +++ b/source/nand/nand.h @@ -76,9 +76,6 @@ u32 GetNandNcsdPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 u32 GetNandPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 index, u32 nand_src); u32 ValidateSecretSector(u8* sector); -u32 GetOtpHash(void* hash); -u32 GetNandCid(void* cid); - bool CheckMultiEmuNand(void); u32 AutoEmuNandBase(bool reset); u32 GetEmuNandBase(void); diff --git a/source/utils/nandutil.c b/source/utils/nandutil.c index 1fa35d3..3d6f3cb 100644 --- a/source/utils/nandutil.c +++ b/source/utils/nandutil.c @@ -3,11 +3,14 @@ #include "firm.h" #include "fatmbr.h" #include "essentials.h" // for essential backup struct +#include "nandcmac.h" +#include "agbsave.h" #include "image.h" #include "fsinit.h" #include "fsperm.h" #include "sighax.h" #include "unittype.h" +#include "sdmmc.h" #include "sha.h" #include "ui.h" #include "vff.h" @@ -81,7 +84,7 @@ u32 BuildEssentialBackup(const char* path, EssentialBackup* essential) { InitImgFS(path_bak); // fill nand cid / otp hash - if (GetNandCid(&(essential->nand_cid)) != 0) return 1; + sdmmc_get_cid(1, (u32*) (void*) &(essential->nand_cid)); if (!IS_UNLOCKED) memset(&(filelist[5]), 0, 3 * sizeof(ExeFsFileHeader)); else memcpy(&(essential->otp), (u8*) 0x10012000, 0x100); @@ -117,6 +120,62 @@ u32 EmbedEssentialBackup(const char* path) { return 0; } +u32 DumpGbaVcSavegame(const char* path) { + if (TEMP_BUFFER_SIZE < AGBSAVE_MAX_SIZE) return 1; + AgbSaveHeader* agbsave = (AgbSaveHeader*) (void*) TEMP_BUFFER; + u8* savegame = (u8*) (agbsave + 1); + + // read full AGBsave to memory + if ((fvx_qread(path, agbsave, 0, sizeof(AgbSaveHeader), NULL) != FR_OK) || (ValidateAgbSaveHeader(agbsave) != 0) || + (fvx_qread(path, savegame, sizeof(AgbSaveHeader), agbsave->save_size, NULL) != FR_OK)) return 1; // not a proper AGBSAVE file + + // byteswap for eeprom type saves (512 byte / 8 kB) + if ((agbsave->save_size == GBASAVE_EEPROM_512) || (agbsave->save_size == GBASAVE_EEPROM_8K)) { + for (u8* ptr = savegame; (ptr - savegame) < (int) agbsave->save_size; ptr += 8) + *(u64*) (void*) ptr = getbe64(ptr); + } + + // generate output path + char path_vcsav[64]; + snprintf(path_vcsav, 64, OUTPUT_PATH "/%016llX.gbavc.sav", agbsave->title_id); + if (fvx_qwrite(path_vcsav, savegame, 0, agbsave->save_size, NULL) != FR_OK) return 1; // write fail + + return 0; +} + +u32 InjectGbaVcSavegame(const char* path, const char* path_vcsave) { + if (TEMP_BUFFER_SIZE < AGBSAVE_MAX_SIZE) return 1; + AgbSaveHeader* agbsave = (AgbSaveHeader*) (void*) TEMP_BUFFER; + u8* savegame = (u8*) (agbsave + 1); + + // basic sanity checks for path_vcsave + FILINFO fno; + char* ext = strrchr(path_vcsave, '.'); + if (!ext || (strncasecmp(++ext, "sav", 4) != 0)) return 1; // bad extension + if ((fvx_stat(path_vcsave, &fno) != FR_OK) || !GBASAVE_VALID(fno.fsize)) + return 1; // bad size + + // read AGBsave header, savegame to memory + if ((fvx_qread(path, agbsave, 0, sizeof(AgbSaveHeader), NULL) != FR_OK) || (ValidateAgbSaveHeader(agbsave) != 0) || + (fvx_qread(path_vcsave, savegame, 0, agbsave->save_size, NULL) != FR_OK)) return 1; // not a proper savegame for header + + // byteswap for eeprom type saves (512 byte / 8 kB) + if ((agbsave->save_size == GBASAVE_EEPROM_512) || (agbsave->save_size == GBASAVE_EEPROM_8K)) { + for (u8* ptr = savegame; (ptr - savegame) < (int) agbsave->save_size; ptr += 8) + *(u64*) (void*) ptr = getbe64(ptr); + } + + // rewrite AGBSAVE file, fix CMAC + if (fvx_qwrite(path, agbsave, 0, sizeof(AgbSaveHeader) + agbsave->save_size, NULL) != FR_OK) return 1; // write fail + if (FixFileCmac(path) != 0) return 1; // cmac fail (this is not efficient, but w/e) + + // set CFG_BOOTENV to 0x7 so the save is taken over + // https://www.3dbrew.org/wiki/CONFIG_Registers#CFG_BOOTENV + if (strncasecmp(path, "S:/agbsave.bin", 256) == 0) *(u32*) 0x10010000 = 0x7; + + return 0; +} + u32 RebuildNandNcsdHeader(NandNcsdHeader* ncsd) { // signature (retail or dev) u8* signature = (IS_DEVKIT) ? sig_nand_ncsd_dev : sig_nand_ncsd_retail; diff --git a/source/utils/nandutil.h b/source/utils/nandutil.h index 7fe8687..0281049 100644 --- a/source/utils/nandutil.h +++ b/source/utils/nandutil.h @@ -8,3 +8,5 @@ u32 FixNandHeader(const char* path, bool check_size); u32 ValidateNandDump(const char* path); u32 SafeRestoreNandDump(const char* path); u32 SafeInstallFirm(const char* path, u32 slots); +u32 DumpGbaVcSavegame(const char* path); +u32 InjectGbaVcSavegame(const char* path, const char* path_vcsave); diff --git a/source/virtual/vnand.c b/source/virtual/vnand.c index cc15595..322783e 100644 --- a/source/virtual/vnand.c +++ b/source/virtual/vnand.c @@ -4,9 +4,8 @@ #include "essentials.h" #include "unittype.h" -#define VFLAG_MBR (1UL<<27) -#define VFLAG_ESSENTIAL (1UL<<28) -#define VFLAG_GBA_VC (1UL<<29) +#define VFLAG_MBR (1UL<<28) +#define VFLAG_ESSENTIAL (1UL<<29) #define VFLAG_NEEDS_OTP (1UL<<30) #define VFLAG_NAND_SIZE (1UL<<31) @@ -27,7 +26,6 @@ static const VirtualNandTemplate vNandTemplates[] = { { "twln.bin" , NP_TYPE_FAT , NP_SUBTYPE_TWL , 0, 0 }, { "twlp.bin" , NP_TYPE_FAT , NP_SUBTYPE_TWL , 1, 0 }, { "agbsave.bin" , NP_TYPE_AGB , NP_SUBTYPE_CTR , 0, VFLAG_DELETABLE }, - { "gbavc.sav" , NP_TYPE_AGB , NP_SUBTYPE_CTR , 0, VFLAG_DELETABLE | VFLAG_GBA_VC }, { "firm0.bin" , NP_TYPE_FIRM , NP_SUBTYPE_CTR , 0, 0 }, { "firm1.bin" , NP_TYPE_FIRM , NP_SUBTYPE_CTR , 1, 0 }, { "firm2.bin" , NP_TYPE_FIRM , NP_SUBTYPE_CTR , 2, 0 }, @@ -73,7 +71,7 @@ bool ReadVNandDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir // handle special cases if (!vfile->size) continue; - if ((nand_src == VRT_XORPAD) && ((vfile->keyslot == 0x11) || (vfile->keyslot >= 0x40) || (vfile->flags & VFLAG_GBA_VC))) + if ((nand_src == VRT_XORPAD) && ((vfile->keyslot == 0x11) || (vfile->keyslot >= 0x40))) continue; if ((vfile->keyslot == 0x05) && !CheckSlot0x05Crypto()) continue; // keyslot 0x05 not properly set up @@ -90,12 +88,6 @@ bool ReadVNandDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir if (memcmp(data, magic, sizeof(magic)) != 0) continue; vfile->size = sizeof(EssentialBackup); } - if (vfile->flags & VFLAG_GBA_VC) { - if (CheckAgbSaveCmac(nand_src) != 0) continue; - vfile->offset += 0x200; - vfile->size = GetAgbSaveSize(nand_src); - } - // found if arriving here vfile->flags |= nand_src; @@ -113,7 +105,6 @@ int ReadVNandFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 count) int WriteVNandFile(const VirtualFile* vfile, const void* buffer, u64 offset, u64 count) { u32 nand_dst = vfile->flags & (VRT_SYSNAND|VRT_EMUNAND|VRT_IMGNAND|VRT_XORPAD); int res = WriteNandBytes(buffer, vfile->offset + offset, count, vfile->keyslot, nand_dst); - if ((res == 0) && (vfile->flags & VFLAG_GBA_VC)) res = FixAgbSaveCmac(nand_dst); return res; }