mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 13:42:47 +00:00
Enable decryption of CIA/NCCH/NCSD files
This commit is contained in:
parent
f53d2808cf
commit
9eb80b375a
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -33,3 +33,4 @@ typedef struct {
|
||||
|
||||
u32 ValidateNcsdHeader(NcsdHeader* header);
|
||||
u32 GetNcsdTrimmedSize(NcsdHeader* header);
|
||||
u32 DecryptNcsdSequential(u8* data, u32 offset_data, u32 size_data);
|
||||
|
308
source/godmode.c
308
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user