diff --git a/source/common.h b/source/common.h index 6d66c1f..5a1b785 100644 --- a/source/common.h +++ b/source/common.h @@ -38,7 +38,7 @@ (((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v)) // GodMode9 version -#define VERSION "0.8.5" +#define VERSION "0.8.6" // input / output paths #define INPUT_PATHS "0:", "0:/files9", "0:/Decrypt9" diff --git a/source/fs/filetype.h b/source/fs/filetype.h index 9af1288..d95f5c1 100644 --- a/source/fs/filetype.h +++ b/source/fs/filetype.h @@ -14,5 +14,6 @@ #define FTYPE_MOUNTABLE (IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS) #define FYTPE_VERIFICABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD) +#define FYTPE_DECRYPTABLE (GAME_CIA|GAME_NCSD|GAME_NCCH) u32 IdentifyFileType(const char* path); diff --git a/source/game/cia.c b/source/game/cia.c index e0dbb43..33ecceb 100644 --- a/source/game/cia.c +++ b/source/game/cia.c @@ -91,6 +91,20 @@ u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk) { return 0; } +u32 FixTmdHashes(TitleMetaData* tmd) { + TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1); + u32 content_count = getbe16(tmd->content_count); + // recalculate content info hashes + for (u32 i = 0, kc = 0; i < 64 && kc < content_count; i++) { + TmdContentInfo* info = tmd->contentinfo + i; + u32 k = getbe16(info->cmd_count); + sha_quick(info->hash, content_list + kc, k * sizeof(TmdContentChunk), SHA256_MODE); + kc += k; + } + sha_quick(tmd->contentinfo_hash, (u8*)tmd->contentinfo, 64 * sizeof(TmdContentInfo), SHA256_MODE); + return 0; +} + u32 BuildCiaCert(u8* ciacert) { const u8 cert_hash_expected[0x20] = { 0xC7, 0x2E, 0x1C, 0xA5, 0x61, 0xDC, 0x9B, 0xC8, 0x05, 0x58, 0x58, 0x9C, 0x63, 0x08, 0x1C, 0x8A, @@ -171,7 +185,7 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) { return 0; } -u32 DecryptCiaContent(u8* data, u32 size, u8* ctr, const u8* titlekey) { +u32 DecryptCiaContentSequential(u8* data, u32 size, u8* ctr, const u8* titlekey) { // WARNING: size and offset of data have to be a multiple of 16 u8 tik[16] __attribute__((aligned(32))); u32 mode = AES_CNT_TITLEKEY_DECRYPT_MODE; diff --git a/source/game/cia.h b/source/game/cia.h index 874f1ca..1d576f5 100644 --- a/source/game/cia.h +++ b/source/game/cia.h @@ -152,6 +152,7 @@ u32 GetCiaInfo(CiaInfo* info, CiaHeader* header); u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd); u32 GetTitleKey(u8* titlekey, Ticket* ticket); u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk); +u32 FixTmdHashes(TitleMetaData* tmd); u32 BuildCiaCert(u8* ciacert); u32 BuildFakeTicket(Ticket* ticket, u8* title_id); @@ -160,4 +161,4 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents); /*u32 BuildCiaMeta(Ncch ncch); u32 InsertCiaContent(const char* path, const char* content, bool encrypt);*/ -u32 DecryptCiaContent(u8* data, u32 size, u8* ctr, const u8* titlekey); +u32 DecryptCiaContentSequential(u8* data, u32 size, u8* ctr, const u8* titlekey); diff --git a/source/game/gameutil.c b/source/game/gameutil.c index 8aba970..baef6f1 100644 --- a/source/game/gameutil.c +++ b/source/game/gameutil.c @@ -7,6 +7,31 @@ #include "sha.h" #include "ff.h" +u32 GetOutputPath(char* dest, const char* path, const char* ext) { + // special handling for input from title directories (somewhat hacky) + if ((strspn(path, "AB147") > 0) && (strncmp(path + 1, ":/title/", 8) == 0)) { + u32 tid_high, tid_low, app_id; + char drv; + if ((sscanf(path, "%c:/title/%08lx/%08lx/content/%08lx", &drv, &tid_high, &tid_low, &app_id) == 4) && + (strnlen(path, 256) == 2 + 1 + 5 + + 1 + 8 + 1 + 8 + 1 + 7 + 1 + 8 + 1 + 3)) { // confused? ^_^ + if (!ext) snprintf(dest, 256, "%s/%08lx%08lx.%08lx.app", OUTPUT_PATH, tid_high, tid_low, app_id); + else snprintf(dest, 256, "%s/%08lx%08lx.%s", OUTPUT_PATH, tid_high, tid_low, ext); + return 0; + } + } + + // handling for everything else + char* name = strrchr(path, '/'); + if (!name) return 1; + snprintf(dest, 256, "%s/%s%s%s", OUTPUT_PATH, ++name, ext ? "." : "", ext ? ext : ""); + + // ensure the output dir exists + if ((f_stat(OUTPUT_PATH, NULL) != FR_OK) && (f_mkdir(OUTPUT_PATH) != FR_OK)) + return 1; + + return 0; +} + u32 GetNcchFileHeaders(NcchHeader* ncch, ExeFsHeader* exefs, FIL* file) { u32 offset_ncch = f_tell(file); UINT btr; @@ -109,6 +134,26 @@ u32 LoadTmdFile(TitleMetaData* tmd, const char* path) { return 0; } +u32 WriteCiaStub(CiaStub* stub, const char* path) { + FIL file; + UINT btw; + CiaInfo info; + + GetCiaInfo(&info, &(stub->header)); + + // everything up till content offset + if (fx_open(&file, path, FA_WRITE | FA_OPEN_ALWAYS) != FR_OK) + return 1; + f_lseek(&file, 0); + if ((fx_write(&file, stub, info.offset_content, &btw) != FR_OK) || (btw != info.offset_content)) { + fx_close(&file); + return 1; + } + + fx_close(&file); + return 0; +} + u32 VerifyTmdContent(const char* path, u64 offset, TmdContentChunk* chunk, const u8* titlekey) { u8 hash[32]; u8 ctr[16]; @@ -133,7 +178,7 @@ u32 VerifyTmdContent(const char* path, u64 offset, TmdContentChunk* chunk, const u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i)); UINT bytes_read; fx_read(&file, MAIN_BUFFER, read_bytes, &bytes_read); - if (encrypted) DecryptCiaContent(MAIN_BUFFER, read_bytes, ctr, titlekey); + if (encrypted) DecryptCiaContentSequential(MAIN_BUFFER, read_bytes, ctr, titlekey); sha_update(MAIN_BUFFER, read_bytes); if (!ShowProgress(i + read_bytes, size, path)) break; } @@ -333,3 +378,218 @@ u32 VerifyGameFile(const char* path) { return VerifyTmdFile(path); else return 1; } + +u32 CheckEncryptedNcchFile(const char* path, u32 offset) { + NcchHeader ncch; + FIL file; + UINT btr; + + // open file, get NCCH header + if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) + return 1; + f_lseek(&file, offset); + if ((fx_read(&file, &ncch, sizeof(NcchHeader), &btr) != FR_OK) || + (ValidateNcchHeader(&ncch) != 0)) { + fx_close(&file); + return 1; + } + fx_close(&file); + + return (NCCH_ENCRYPTED(&ncch)) ? 0 : 1; +} + +u32 CheckEncryptedNcsdFile(const char* path) { + NcsdHeader ncsd; + + // load NCSD header + if (LoadNcsdHeader(&ncsd, path) != 0) + return 1; + + // check for encryption in NCSD contents + for (u32 i = 0; i < 8; i++) { + NcchPartition* partition = ncsd.partitions + i; + u32 offset = partition->offset * NCSD_MEDIA_UNIT; + if (!partition->size) continue; + if (CheckEncryptedNcchFile(path, offset) == 0) + return 0; + } + + return 1; +} + +u32 CheckEncryptedCiaFile(const char* path) { + CiaStub* cia = (CiaStub*) TEMP_BUFFER; + CiaInfo info; + + // load CIA stub + if ((LoadCiaStub(cia, path) != 0) || + (GetCiaInfo(&info, &(cia->header)) != 0)) + return 1; + + // check for encryption in CIA contents + u32 content_count = getbe16(cia->tmd.content_count); + u64 next_offset = info.offset_content; + for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) { + TmdContentChunk* chunk = &(cia->content_list[i]); + if ((getbe16(chunk->type) & 0x1) || (CheckEncryptedNcchFile(path, next_offset))) + return 0; // encryption found + next_offset += getbe64(chunk->size); + } + + return 1; +} + +u32 CheckEncryptedGameFile(const char* path) { + u32 filetype = IdentifyFileType(path); + if (filetype == GAME_CIA) + return CheckEncryptedCiaFile(path); + else if (filetype == GAME_NCSD) + return CheckEncryptedNcsdFile(path); + else if (filetype == GAME_NCCH) + return CheckEncryptedNcchFile(path, 0); + else return 1; +} + +u32 DecryptNcchNcsdFile(const char* orig, const char* dest, u32 mode, + u32 offset, u32 size, TmdContentChunk* chunk, const u8* titlekey) { // this line only for CIA contents + // this will do a simple copy for unencrypted files + bool inplace = (strncmp(orig, dest, 256) == 0); + FIL ofile; + FIL dfile; + FIL* ofp = &ofile; + FIL* dfp = (inplace) ? &ofile : &dfile; + FSIZE_t fsize; + + // open file(s) + if (inplace) { + if (fx_open(ofp, orig, FA_READ | FA_WRITE | FA_OPEN_EXISTING) != FR_OK) + return 1; + f_lseek(ofp, offset); + } else { + if (fx_open(ofp, orig, FA_READ | FA_OPEN_EXISTING) != FR_OK) + return 1; + if (fx_open(dfp, dest, FA_WRITE | (offset ? FA_OPEN_ALWAYS : FA_CREATE_ALWAYS)) != FR_OK) { + fx_close(ofp); + return 1; + } + f_lseek(ofp, offset); + f_lseek(dfp, offset); + } + + fsize = f_size(ofp); // for progress bar + if (!size) size = fsize; + + u32 ret = 0; + if (mode & (GAME_NCCH|GAME_NCSD)) { // for NCCH / NCSD files + if (!ShowProgress(offset, fsize, dest)) ret = 1; + for (u32 i = 0; (i < size) && (ret == 0); i += MAIN_BUFFER_SIZE) { + u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i)); + UINT bytes_read, bytes_written; + if (fx_read(ofp, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) ret = 1; + if (((mode & GAME_NCCH) && (DecryptNcchSequential(MAIN_BUFFER, i, read_bytes) != 0)) || + ((mode & GAME_NCSD) && (DecryptNcsdSequential(MAIN_BUFFER, i, read_bytes) != 0))) + ret = 1; + if (inplace) f_lseek(ofp, f_tell(ofp) - read_bytes); + if (fx_write(dfp, MAIN_BUFFER, read_bytes, &bytes_written) != FR_OK) ret = 1; + if ((read_bytes != bytes_read) || (bytes_read != bytes_written)) ret = 1; + if (!ShowProgress(offset + i + read_bytes, fsize, dest)) ret = 1; + } + } else if (mode & GAME_CIA) { // for NCCHs inside CIAs + if (!ShowProgress(offset, fsize, dest)) ret = 1; + bool cia_crypto = getbe16(chunk->type) & 0x1; + bool ncch_crypto; // find out by decrypting the NCCH header + UINT bytes_read, bytes_written; + u8 ctr[16]; + + GetTmdCtr(ctr, chunk); // NCCH crypto? + if (fx_read(ofp, MAIN_BUFFER, sizeof(NcchHeader), &bytes_read) != FR_OK) ret = 1; + if (cia_crypto) DecryptCiaContentSequential(MAIN_BUFFER, sizeof(NcchHeader), ctr, titlekey); + ncch_crypto = ((ValidateNcchHeader((NcchHeader*) (void*) MAIN_BUFFER) == 0) && + NCCH_ENCRYPTED((NcchHeader*) (void*) MAIN_BUFFER)); + if (ncch_crypto && (SetupNcchCrypto((NcchHeader*) (void*) MAIN_BUFFER) != 0)) { + ShowPrompt(false, "Error: Cannot set up NCCH crypto"); + }; + + GetTmdCtr(ctr, chunk); + f_lseek(ofp, offset); + sha_init(SHA256_MODE); + for (u32 i = 0; (i < size) && (ret == 0); i += MAIN_BUFFER_SIZE) { + u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i)); + if (fx_read(ofp, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) ret = 1; + if (cia_crypto && (DecryptCiaContentSequential(MAIN_BUFFER, read_bytes, ctr, titlekey) != 0)) ret = 1; + if (ncch_crypto && (DecryptNcchSequential(MAIN_BUFFER, i, read_bytes) != 0)) ret = 1; + if (inplace) f_lseek(ofp, f_tell(ofp) - read_bytes); + if (fx_write(dfp, MAIN_BUFFER, read_bytes, &bytes_written) != FR_OK) ret = 1; + sha_update(MAIN_BUFFER, read_bytes); + if ((read_bytes != bytes_read) || (bytes_read != bytes_written)) ret = 1; + if (!ShowProgress(offset + i + read_bytes, fsize, dest)) ret = 1; + } + sha_get(chunk->hash); + chunk->type[1] &= ~0x01; + } + + fx_close(ofp); + if (!inplace) fx_close(dfp); + + return ret; +} + +u32 DecryptCiaFile(const char* orig, const char* dest) { + bool inplace = (strncmp(orig, dest, 256) == 0); + CiaStub* cia = (CiaStub*) TEMP_BUFFER; + CiaInfo info; + u8 titlekey[16]; + + // start operation + if (!ShowProgress(0, 0, orig)) return 1; + + // if not inplace: clear destination + if (!inplace) f_unlink(dest); + + // load CIA stub from origin + if ((LoadCiaStub(cia, orig) != 0) || + (GetCiaInfo(&info, &(cia->header)) != 0) || + (GetTitleKey(titlekey, &(cia->ticket)) != 0)) { + return 1; + } + + // decrypt CIA contents + u32 content_count = getbe16(cia->tmd.content_count); + u64 next_offset = info.offset_content; + for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) { + TmdContentChunk* chunk = &(cia->content_list[i]); + u64 size = getbe64(chunk->size); + if (DecryptNcchNcsdFile(orig, dest, GAME_CIA, next_offset, size, chunk, titlekey) != 0) + return 1; + next_offset += size; + } + + // fix TMD hashes, write CIA stub to destination + if ((FixTmdHashes(&(cia->tmd)) != 0) || + (WriteCiaStub(cia, dest) != 0)) return 1; + + return 0; +} + +u32 DecryptGameFile(const char* path, bool inplace) { + u32 filetype = IdentifyFileType(path); + char dest[256]; + char* destptr = (char*) path; + u32 ret = 0; + + if (!inplace) { + if (GetOutputPath(dest, path, NULL) != 0) return 1; + destptr = dest; + } + + if (filetype & GAME_CIA) + ret = DecryptCiaFile(path, destptr); + else if (filetype & (GAME_NCCH|GAME_NCSD)) + ret = DecryptNcchNcsdFile(path, destptr, filetype, 0, 0, NULL, NULL); + else ret = 1; + + if (!inplace && (ret != 0)) + f_unlink(dest); // try to get rid of the borked file + + return ret; +} diff --git a/source/game/gameutil.h b/source/game/gameutil.h index c1bda12..11f6748 100644 --- a/source/game/gameutil.h +++ b/source/game/gameutil.h @@ -2,11 +2,6 @@ #include "common.h" -u32 VerifyNcchFile(const char* path, u32 offset, u32 size); -u32 VerifyNcsdFile(const char* path); -u32 VerifiyCiaFile(const char* path); u32 VerifyGameFile(const char* path); - -u32 DecryptNcchFile(const char* path, u32 offset, u32 size); -u32 DecryptNcsdFile(const char* path); -u32 DecryptCiaFile(const char* path, bool deep); +u32 CheckEncryptedGameFile(const char* path); +u32 DecryptGameFile(const char* path, bool inplace); diff --git a/source/game/ncch.c b/source/game/ncch.c index 4eeb280..ccfc83c 100644 --- a/source/game/ncch.c +++ b/source/game/ncch.c @@ -260,7 +260,7 @@ u32 DecryptNcch(u8* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* e if (DecryptNcchSection(data, offset, size, ncch->offset_exefs * NCCH_MEDIA_UNIT, 0x200, 0, ncch, 2, 0) != 0) return 1; - + // exefs file handling if (exefs) for (u32 i = 0; i < 10; i++) { ExeFsFileHeader* file = exefs->files + i; @@ -281,3 +281,35 @@ u32 DecryptNcch(u8* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* e return 0; } + +// on the fly decryptor for NCCH - sequential +u32 DecryptNcchSequential(u8* data, u32 offset, u32 size) { + // warning: this will only work for sequential processing + // unexpected results otherwise + static NcchHeader ncch = { 0 }; + static ExeFsHeader exefs = { 0 }; + static ExeFsHeader* exefsptr = NULL; + + // fetch ncch header from data + if ((offset == 0) && (size >= sizeof(NcchHeader))) { + memcpy(&ncch, data, sizeof(NcchHeader)); + exefsptr = NULL; + } + + // fetch exefs header from data + if (!exefsptr) { + u32 offset_exefs = ncch.offset_exefs * NCCH_MEDIA_UNIT; + if ((offset <= offset_exefs) && + ((offset + size) >= offset_exefs + sizeof(ExeFsHeader))) { + if (DecryptNcch(data, offset, offset_exefs + sizeof(ExeFsHeader) - offset, &ncch, NULL) != 0) + return 1; + memcpy(&exefs, data + offset_exefs - offset, sizeof(ExeFsHeader)); + size -= offset_exefs + sizeof(ExeFsHeader) - offset; + data += offset_exefs + sizeof(ExeFsHeader) - offset; + offset = offset_exefs + sizeof(ExeFsHeader); + exefsptr = &exefs; + } + } + + return DecryptNcch(data, offset, size, &ncch, exefsptr); +} diff --git a/source/game/ncch.h b/source/game/ncch.h index d570cee..fad3ee6 100644 --- a/source/game/ncch.h +++ b/source/game/ncch.h @@ -8,7 +8,7 @@ #define NCCH_EXTHDR_SIZE 0x800 // NCCH header says 0x400, which is not the full thing #define NCCH_EXTHDR_OFFSET 0x200 -#define NCCH_ENCRYPTED(ncch) (!(ncch->flags[7] & 0x04)) +#define NCCH_ENCRYPTED(ncch) (!((ncch)->flags[7] & 0x04)) // see: https://www.3dbrew.org/wiki/NCCH/Extended_Header // very limited, contains only required stuff @@ -62,3 +62,4 @@ typedef struct { u32 ValidateNcchHeader(NcchHeader* header); u32 SetupNcchCrypto(NcchHeader* ncch); u32 DecryptNcch(u8* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs); +u32 DecryptNcchSequential(u8* data, u32 offset, u32 size); diff --git a/source/game/ncsd.c b/source/game/ncsd.c index bedadd2..84a8eac 100644 --- a/source/game/ncsd.c +++ b/source/game/ncsd.c @@ -1,4 +1,5 @@ #include "ncsd.h" +#include "ncch.h" u32 ValidateNcsdHeader(NcsdHeader* header) { u8 zeroes[16] = { 0 }; @@ -32,3 +33,41 @@ u32 GetNcsdTrimmedSize(NcsdHeader* header) { return data_units * NCSD_MEDIA_UNIT; } + +// on the fly decryptor for NCSD +u32 DecryptNcsdSequential(u8* data, u32 offset_data, u32 size_data) { + // warning: this will only work for sequential processing + static NcsdHeader ncsd; + + // fetch ncsd header from data + if ((offset_data == 0) && (size_data >= sizeof(NcsdHeader))) + memcpy(&ncsd, data, sizeof(NcsdHeader)); + + for (u32 i = 0; i < 8; i++) { + NcchPartition* partition = ncsd.partitions + i; + u32 offset_p = partition->offset * NCSD_MEDIA_UNIT; + u32 size_p = partition->size * NCSD_MEDIA_UNIT; + // check if partition in data + if ((offset_p >= offset_data + size_data) || + (offset_data >= offset_p + size_p) || + !size_p) { + continue; // section not in data + } + // determine data / offset / size + u8* data_i = data; + u32 offset_i = 0; + u32 size_i = size_p; + if (offset_p < offset_data) + offset_i = offset_data - offset_p; + else data_i = data + (offset_p - offset_data); + size_i = size_p - offset_i; + if (size_i > size_data - (data_i - data)) + size_i = size_data - (data_i - data); + // decrypt ncch segment + if (DecryptNcchSequential(data_i, offset_i, size_i) != 0) + return 1; + } + + return 0; +} + diff --git a/source/game/ncsd.h b/source/game/ncsd.h index 2a08a46..dc1b567 100644 --- a/source/game/ncsd.h +++ b/source/game/ncsd.h @@ -33,3 +33,4 @@ typedef struct { u32 ValidateNcsdHeader(NcsdHeader* header); u32 GetNcsdTrimmedSize(NcsdHeader* header); +u32 DecryptNcsdSequential(u8* data, u32 offset_data, u32 size_data); diff --git a/source/godmode.c b/source/godmode.c index ab70bb6..8415ec7 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -559,6 +559,196 @@ u32 Sha256Calculator(const char* path) { return 0; } +u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* current_dir, DirStruct* clipboard) { + DirEntry* curr_entry = &(current_dir->entry[*cursor]); + const char* optionstr[8]; + + u32 filetype = IdentifyFileType(curr_entry->path); + u32 drvtype = DriveType(curr_entry->path); + + // special stuff, only available on FAT drives (see int special below) + bool mountable = ((filetype & FTYPE_MOUNTABLE) && !(drvtype & (DRV_IMAGE|DRV_RAMDRIVE))); + bool verificable = (filetype & FYTPE_VERIFICABLE); + bool decryptable = (filetype & FYTPE_DECRYPTABLE); + bool decryptable_inplace = (decryptable && (drvtype & (DRV_SDCARD|DRV_RAMDRIVE))); + + char pathstr[32 + 1]; + TruncateString(pathstr, curr_entry->path, 32, 8); + if (!FileCheck(curr_entry->path)) ShowPrompt(false, "%s\nFile is currently locked", pathstr); + + // main menu processing + int n_opt = 0; + int special = (filetype && (drvtype & DRV_FAT)) ? ++n_opt : -1; + int hexviewer = ++n_opt; + int calcsha = ++n_opt; + int inject = ((clipboard->n_entries == 1) && + (clipboard->entry[0].type == T_FILE) && + (drvtype & DRV_FAT) && + (strncmp(clipboard->entry[0].path, curr_entry->path, 256) != 0)) ? + (int) ++n_opt : -1; + int searchdrv = (drvtype & DRV_SEARCH) ? ++n_opt : -1; + if (special > 0) optionstr[special-1] = + (filetype == IMG_NAND ) ? "Mount as NAND image" : + (filetype == IMG_FAT ) ? "Mount as FAT image" : + (filetype == GAME_CIA ) ? "CIA image options..." : + (filetype == GAME_NCSD ) ? "NCSD image options..." : + (filetype == GAME_NCCH ) ? "NCCH image options..." : + (filetype == GAME_EXEFS) ? "Mount as EXEFS image" : + (filetype == GAME_ROMFS) ? "Mount as ROMFS image" : + (filetype == GAME_TMD) ? "Verify TMD file" : "???"; + optionstr[hexviewer-1] = "Show in Hexeditor"; + optionstr[calcsha-1] = "Calculate SHA-256"; + if (inject > 0) optionstr[inject-1] = "Inject data @offset"; + if (searchdrv > 0) optionstr[searchdrv-1] = "Open containing folder"; + + int user_select = ShowSelectPrompt(n_opt, optionstr, pathstr); + if (user_select == hexviewer) { // -> show in hex viewer + HexViewer(curr_entry->path); + return 0; + } else if (user_select == calcsha) { // -> calculate SHA-256 + Sha256Calculator(curr_entry->path); + GetDirContents(current_dir, current_path); + return 0; + } 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); + if (offset != (u64) -1) { + if (!FileInjectFile(curr_entry->path, clipboard->entry[0].path, (u32) offset)) + ShowPrompt(false, "Failed injecting %s", origstr); + clipboard->n_entries = 0; + } + return 0; + } 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); + GetDirContents(current_dir, current_path); + *cursor = 1; + *scroll = 0; + } + return 0; + } else if (user_select != special) { + return 1; + } + + // stuff for special menu starts here + n_opt = 0; + int mount = (mountable) ? ++n_opt : -1; + int decrypt = (decryptable) ? ++n_opt : -1; + int decrypt_inplace = (decryptable_inplace) ? ++n_opt : -1; + int verify = (verificable) ? ++n_opt : -1; + if (mount > 0) optionstr[mount-1] = "Mount image to drive"; + if (decrypt > 0) optionstr[decrypt-1] = "Decrypt file (SD output)"; + if (decrypt_inplace > 0) optionstr[decrypt_inplace-1] = "Decrypt file (inplace)"; + if (verify > 0) optionstr[verify-1] = "Verify file"; + + u32 n_marked = 0; + if (curr_entry->marked) { + for (u32 i = 0; i < current_dir->n_entries; i++) + if (current_dir->entry[i].marked) n_marked++; + } + + // auto select when there is only one option + user_select = (n_opt > 1) ? (int) ShowSelectPrompt(n_opt, optionstr, pathstr) : n_opt; + if (user_select == mount) { // -> mount file as image + if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & (DRV_IMAGE|DRV_RAMDRIVE))) + clipboard->n_entries = 0; // remove last mounted image clipboard entries + InitImgFS(curr_entry->path); + if (!(DriveType("7:")||DriveType("8:")||DriveType("9:")||DriveType("G:"))) { + ShowPrompt(false, "Mounting image: failed"); + InitImgFS(NULL); + } else { + *cursor = 0; + *current_path = '\0'; + GetDirContents(current_dir, current_path); + for (u32 i = 0; i < current_dir->n_entries; i++) { + if (strspn(current_dir->entry[i].path, "789GI") == 0) + continue; + strncpy(current_path, current_dir->entry[i].path, 256); + GetDirContents(current_dir, current_path); + *cursor = 1; + *scroll = 0; + break; + } + } + return 0; + } else if ((user_select == decrypt) || (user_select == decrypt_inplace)) { // -> decrypt game file + bool inplace = (user_select == decrypt_inplace); + if ((n_marked > 1) && ShowPrompt(true, "Try to decrypt all %lu selected files?", n_marked)) { + u32 n_success = 0; + u32 n_unencrypted = 0; + u32 n_other = 0; + for (u32 i = 0; i < current_dir->n_entries; i++) { + const char* path = current_dir->entry[i].path; + if (!current_dir->entry[i].marked) + continue; + if (IdentifyFileType(path) != filetype) { + n_other++; + continue; + } + if (CheckEncryptedGameFile(path) != 0) { + n_unencrypted++; + continue; + } + current_dir->entry[i].marked = false; + if (DecryptGameFile(path, inplace) == 0) n_success++; + else { // on failure: set cursor on failed title, break; + *cursor = i; + break; + } + } + if (n_other || n_unencrypted) { + ShowPrompt(false, "%lu/%lu files decrypted ok\n%lu/%lu not encrypted\n%lu/%lu not of same type", + n_success, n_marked, n_unencrypted, n_marked, n_other, n_marked); + } else ShowPrompt(false, "%lu/%lu files decrypted ok", n_success, n_marked); + if (!inplace && n_success) ShowPrompt(false, "%lu files written to %s", n_success, OUTPUT_PATH); + } else { + if (CheckEncryptedGameFile(curr_entry->path) != 0) { + ShowPrompt(false, "%s\nFile is not encrypted", pathstr); + } else { + u32 ret = DecryptGameFile(curr_entry->path, inplace); + if (inplace || (ret != 0)) ShowPrompt(false, "%s\nDecryption %s", pathstr, (ret == 0) ? "success" : "failed"); + else ShowPrompt(false, "%s\nDecrypted to %s", pathstr, OUTPUT_PATH); + } + } + return 0; + } else if (user_select == verify) { // -> verify game file + if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) { + u32 n_success = 0; + u32 n_other = 0; + u32 n_processed = 0; + for (u32 i = 0; i < current_dir->n_entries; i++) { + const char* path = current_dir->entry[i].path; + if (!current_dir->entry[i].marked) + continue; + if (IdentifyFileType(path) != filetype) { + n_other++; + continue; + } + if (!(filetype & (GAME_CIA|GAME_TMD)) && + !ShowProgress(n_processed++, n_marked, path)) break; + current_dir->entry[i].marked = false; + if (VerifyGameFile(path) == 0) n_success++; + else { // on failure: set *cursor on failed title, break; + *cursor = i; + break; + } + } + if (n_other) ShowPrompt(false, "%lu/%lu files verified ok\n%lu/%lu not of same type", + n_success, n_marked, n_other, n_marked); + else ShowPrompt(false, "%lu/%lu files verified ok", n_success, n_marked); + } else { + ShowString("%s\nVerifying file, please wait...", pathstr); + ShowPrompt(false, "%s\nVerification %s", pathstr, + (VerifyGameFile(curr_entry->path) == 0) ? "success" : "failed"); + } + return 0; + } + + return 1; +} + u32 GodMode() { static const u32 quick_stp = 20; u32 exit_mode = GODMODE_EXIT_REBOOT; @@ -664,123 +854,7 @@ u32 GodMode() { } } } else if ((pad_state & BUTTON_A) && (curr_entry->type == T_FILE)) { // process a file - char pathstr[32 + 1]; - const char* optionstr[8]; - int n_opt = 0; - - u32 filetype = IdentifyFileType(curr_entry->path); - u32 drvtype = DriveType(curr_entry->path); - - int mountable = ((filetype & FTYPE_MOUNTABLE) && (drvtype & DRV_FAT) && - !(drvtype & (DRV_IMAGE|DRV_RAMDRIVE))) ? - ++n_opt : -1; - int hexviewer = ++n_opt; - int calcsha = ++n_opt; - int verificable = (filetype & FYTPE_VERIFICABLE) ? ++n_opt : -1; - int injectable = ((clipboard->n_entries == 1) && - (clipboard->entry[0].type == T_FILE) && - (drvtype & DRV_FAT) && - (strncmp(clipboard->entry[0].path, curr_entry->path, 256) != 0)) ? - (int) ++n_opt : -1; - int searchdrv = (curr_drvtype & DRV_SEARCH) ? ++n_opt : -1; - - u32 n_marked = 0; - if (curr_entry->marked) { - for (u32 i = 0; i < current_dir->n_entries; i++) - if (current_dir->entry[i].marked) n_marked++; - } - - TruncateString(pathstr, curr_entry->path, 32, 8); - optionstr[hexviewer-1] = "Show in Hexeditor"; - optionstr[calcsha-1] = "Calculate SHA-256"; - if (verificable > 0) optionstr[verificable-1] = "Verify game image"; - if (injectable > 0) optionstr[injectable-1] = "Inject data @offset"; - if (mountable > 0) optionstr[mountable-1] = - (filetype == IMG_NAND ) ? "Mount as NAND image" : - (filetype == IMG_FAT ) ? "Mount as FAT image" : - (filetype == GAME_CIA ) ? "Mount as CIA image" : - (filetype == GAME_NCSD ) ? "Mount as NCSD image" : - (filetype == GAME_NCCH ) ? "Mount as NCCH image" : - (filetype == GAME_EXEFS) ? "Mount as EXEFS image" : - (filetype == GAME_ROMFS) ? "Mount as ROMFS image" : "???"; - if (searchdrv > 0) optionstr[searchdrv-1] = "Open containing folder"; - - int user_select = 0; - if (!FileCheck(curr_entry->path)) ShowPrompt(false, "%s\nFile is currently locked", pathstr); - else user_select = ShowSelectPrompt(n_opt, optionstr, pathstr); - if (user_select == hexviewer) { // -> show in hex viewer - HexViewer(curr_entry->path); - } else if (user_select == calcsha) { // -> calculate SHA-256 - Sha256Calculator(curr_entry->path); - GetDirContents(current_dir, current_path); - } else if (user_select == verificable) { // -> verify game file - if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) { - u32 n_success = 0; - u32 n_other = 0; - u32 n_processed = 0; - for (u32 i = 0; i < current_dir->n_entries; i++) { - const char* path = current_dir->entry[i].path; - if (!current_dir->entry[i].marked) - continue; - if (IdentifyFileType(path) != filetype) { - n_other++; - continue; - } - if (!(filetype & (GAME_CIA|GAME_TMD)) && - !ShowProgress(n_processed++, n_marked, path)) break; - current_dir->entry[i].marked = false; - if (VerifyGameFile(path) == 0) n_success++; - else { // on failure: set cursor on failed title, break; - cursor = i; - break; - } - } - if (n_other) ShowPrompt(false, "%lu/%lu files verified ok\n%lu/%lu not of same type", - n_success, n_marked, n_other, n_marked); - else ShowPrompt(false, "%lu/%lu files verified ok", n_success, n_marked); - } else { - ShowString("%s\nVerifying file, please wait...", pathstr); - u32 result = VerifyGameFile(curr_entry->path); - ShowPrompt(false, "%s\nVerification %s", pathstr, (result == 0) ? "success" : "failed"); - } - } else if (user_select == injectable) { // -> 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); - if (offset != (u64) -1) { - if (!FileInjectFile(curr_entry->path, clipboard->entry[0].path, (u32) offset)) - ShowPrompt(false, "Failed injecting %s", origstr); - clipboard->n_entries = 0; - } - } else if (user_select == mountable) { // -> mount file as image - if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & (DRV_IMAGE|DRV_RAMDRIVE))) - clipboard->n_entries = 0; // remove last mounted image clipboard entries - InitImgFS(curr_entry->path); - if (!(DriveType("7:")||DriveType("8:")||DriveType("9:")||DriveType("G:"))) { - ShowPrompt(false, "Mounting image: failed"); - InitImgFS(NULL); - } else { - cursor = 0; - *current_path = '\0'; - GetDirContents(current_dir, current_path); - for (u32 i = 0; i < current_dir->n_entries; i++) { - if (strspn(current_dir->entry[i].path, "789GI") == 0) - continue; - strncpy(current_path, current_dir->entry[i].path, 256); - GetDirContents(current_dir, current_path); - cursor = 1; - break; - } - } - } 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); - GetDirContents(current_dir, current_path); - cursor = 1; - scroll = 0; - } - } + FileHandlerMenu(current_path, &cursor, &scroll, current_dir, clipboard); // processed externally } else if (*current_path && ((pad_state & BUTTON_B) || // one level down ((pad_state & BUTTON_A) && (curr_entry->type == T_DOTDOT)))) { if (switched) { // use R+B to return to root fast