forked from Mirror/GodMode9
Added GBA VC save dumper / injector
... this also removes the gbavc.sav file from the virtual NAND drives and does some general cleanup. Fixes #205.
This commit is contained in:
parent
73d4ba0ab8
commit
c9f9745495
@ -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) {
|
||||
|
@ -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);
|
||||
|
121
source/godmode.c
121
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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user