Added NCCH padgen ability

... from ncchinfo.bin
This commit is contained in:
d0k3 2017-01-25 14:46:29 +01:00
parent 8d55cf4a62
commit d9dbf14f8b
9 changed files with 218 additions and 39 deletions

View File

@ -8,8 +8,12 @@ u32 IdentifyFileType(const char* path) {
const u8 firm_magic[] = { FIRM_MAGIC }; const u8 firm_magic[] = { FIRM_MAGIC };
u8 header[0x200] __attribute__((aligned(32))); // minimum required size u8 header[0x200] __attribute__((aligned(32))); // minimum required size
size_t fsize = FileGetSize(path); size_t fsize = FileGetSize(path);
if (FileGetData(path, header, 0x200, 0) != 0x200) return 0; char* fname = strrchr(path, '/');
if (fname == NULL) return 0; // not a proper path
fname++;
if (FileGetData(path, header, 0x200, 0) < fsize) return 0;
if (fsize >= 0x200) {
if ((getbe32(header + 0x100) == 0x4E435344) && (getbe64(header + 0x110) == (u64) 0x0104030301000000) && if ((getbe32(header + 0x100) == 0x4E435344) && (getbe64(header + 0x110) == (u64) 0x0104030301000000) &&
(getbe64(header + 0x108) == (u64) 0) && (fsize >= 0x8FC8000)) { (getbe64(header + 0x108) == (u64) 0) && (fsize >= 0x8FC8000)) {
return IMG_NAND; // NAND image return IMG_NAND; // NAND image
@ -33,9 +37,9 @@ u32 IdentifyFileType(const char* path) {
} else if (ValidateNcchHeader((NcchHeader*) (void*) header) == 0) { } else if (ValidateNcchHeader((NcchHeader*) (void*) header) == 0) {
NcchHeader* ncch = (NcchHeader*) (void*) header; NcchHeader* ncch = (NcchHeader*) (void*) header;
if (fsize >= (ncch->size * NCCH_MEDIA_UNIT)) if (fsize >= (ncch->size * NCCH_MEDIA_UNIT))
return GAME_NCCH; // NCSD (".3DS") file return GAME_NCCH; // NCCH (".APP") file
} else if (ValidateExeFsHeader((ExeFsHeader*) (void*) header, fsize) == 0) { } else if (ValidateExeFsHeader((ExeFsHeader*) (void*) header, fsize) == 0) {
return GAME_EXEFS; // ExeFS file return GAME_EXEFS; // ExeFS file (false positives possible)
} else if (memcmp(header, romfs_magic, sizeof(romfs_magic)) == 0) { } else if (memcmp(header, romfs_magic, sizeof(romfs_magic)) == 0) {
return GAME_ROMFS; // RomFS file (check could be better) return GAME_ROMFS; // RomFS file (check could be better)
} else if (strncmp(TMD_ISSUER, (char*) (header + 0x140), 0x40) == 0) { } else if (strncmp(TMD_ISSUER, (char*) (header + 0x140), 0x40) == 0) {
@ -44,6 +48,12 @@ u32 IdentifyFileType(const char* path) {
} else if (memcmp(header, firm_magic, sizeof(firm_magic)) == 0) { } else if (memcmp(header, firm_magic, sizeof(firm_magic)) == 0) {
return SYS_FIRM; // FIRM file return SYS_FIRM; // FIRM file
} }
}
if ((fsize > sizeof(NcchInfoHeader)) &&
(GetNcchInfoVersion((NcchInfoHeader*) (void*) header)) &&
(strncasecmp(fname, NCCHINFO_NAME, 32) == 0)) {
return MISC_NINFO; // ncchinfo.bin file
}
return 0; return 0;
} }

View File

@ -14,11 +14,14 @@
#define SYS_FIRM (1<<8) #define SYS_FIRM (1<<8)
#define MISC_NINFO (1<<9)
#define FTYPE_MOUNTABLE (IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|SYS_FIRM) #define FTYPE_MOUNTABLE (IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|SYS_FIRM)
#define FYTPE_VERIFICABLE (IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|SYS_FIRM) #define FYTPE_VERIFICABLE (IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|SYS_FIRM)
#define FYTPE_DECRYPTABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|SYS_FIRM) #define FYTPE_DECRYPTABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|SYS_FIRM)
#define FTYPE_BUILDABLE (GAME_NCSD|GAME_NCCH|GAME_TMD) #define FTYPE_BUILDABLE (GAME_NCSD|GAME_NCCH|GAME_TMD)
#define FTYPE_BUILDABLE_L (FTYPE_BUILDABLE&(GAME_TMD)) #define FTYPE_BUILDABLE_L (FTYPE_BUILDABLE&(GAME_TMD))
#define FTYPE_RESTORABLE (IMG_NAND) #define FTYPE_RESTORABLE (IMG_NAND)
#define FTYPE_XORPAD (MISC_NINFO)
u32 IdentifyFileType(const char* path); u32 IdentifyFileType(const char* path);

View File

@ -6,3 +6,4 @@
#include "exefs.h" #include "exefs.h"
#include "romfs.h" #include "romfs.h"
#include "firm.h" #include "firm.h"
#include "ncchinfo.h"

View File

@ -1128,3 +1128,51 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) {
return ret; return ret;
} }
u32 BuildNcchInfoXorpads(const char* destdir, const char* path) {
FIL fp_info;
FIL fp_xorpad;
UINT bt;
if (!CheckWritePermissions(destdir)) return 1;
NcchInfoHeader info;
u32 version = 0;
u32 entry_size = 0;
u32 ret = 0;
if (fvx_open(&fp_info, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 1;
fvx_lseek(&fp_info, 0);
if ((fvx_read(&fp_info, &info, sizeof(NcchInfoHeader), &bt) != FR_OK) ||
(bt != sizeof(NcchInfoHeader))) {
fvx_close(&fp_info);
return 1;
}
version = GetNcchInfoVersion(&info);
entry_size = (version == 3) ? NCCHINFO_V3_SIZE : sizeof(NcchInfoEntry);
if (!version) ret = 1;
for (u32 i = 0; (i < info.n_entries) && (ret == 0); i++) {
NcchInfoEntry entry;
if ((fvx_read(&fp_info, &entry, entry_size, &bt) != FR_OK) ||
(bt != entry_size)) ret = 1;
if (FixNcchInfoEntry(&entry, version) != 0) ret = 1;
if (ret != 0) break;
char dest[256]; // 256 is the maximum length of a full path
snprintf(dest, 256, "%s/%s", destdir, entry.filename);
if (fvx_open(&fp_xorpad, dest, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK)
ret = 1;
if (!ShowProgress(0, 0, entry.filename)) ret = 1;
for (u64 p = 0; (p < entry.size_b) && (ret == 0); p += MAIN_BUFFER_SIZE) {
UINT create_bytes = min(MAIN_BUFFER_SIZE, entry.size_b - p);
if (BuildNcchInfoXorpad(MAIN_BUFFER, &entry, create_bytes, p) != 0) ret = 1;
if (fvx_write(&fp_xorpad, MAIN_BUFFER, create_bytes, &bt) != FR_OK) ret = 1;
if (!ShowProgress(p + create_bytes, entry.size_b, entry.filename)) ret = 1;
}
fvx_close(&fp_xorpad);
if (ret != 0) f_unlink(dest); // get rid of the borked file
}
fvx_close(&fp_info);
return ret;
}

View File

@ -6,3 +6,4 @@ u32 VerifyGameFile(const char* path);
u32 CheckEncryptedGameFile(const char* path); u32 CheckEncryptedGameFile(const char* path);
u32 DecryptGameFile(const char* path, bool inplace); u32 DecryptGameFile(const char* path, bool inplace);
u32 BuildCiaFromGameFile(const char* path, bool force_legit); u32 BuildCiaFromGameFile(const char* path, bool force_legit);
u32 BuildNcchInfoXorpads(const char* destdir, const char* path);

View File

@ -60,6 +60,7 @@ typedef struct {
} __attribute__((packed, aligned(16))) NcchHeader; } __attribute__((packed, aligned(16))) NcchHeader;
u32 ValidateNcchHeader(NcchHeader* header); u32 ValidateNcchHeader(NcchHeader* header);
u32 SetNcchKey(NcchHeader* ncch, u32 keyid);
u32 SetupNcchCrypto(NcchHeader* ncch); u32 SetupNcchCrypto(NcchHeader* ncch);
u32 DecryptNcch(u8* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs); u32 DecryptNcch(u8* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs);
u32 DecryptNcchSequential(u8* data, u32 offset, u32 size); u32 DecryptNcchSequential(u8* data, u32 offset, u32 size);

59
source/game/ncchinfo.c Normal file
View File

@ -0,0 +1,59 @@
#include "ncchinfo.h"
#include "ncch.h"
#include "aes.h"
u32 GetNcchInfoVersion(NcchInfoHeader* info) {
if (!info->n_entries) return 0; // cannot be empty
if (info->ncch_info_version == NCCHINFO_V3_MAGIC) return 3;
else if (info->ncch_info_version == NCCHINFO_V4_MAGIC) return 4;
else return 0;
}
u32 FixNcchInfoEntry(NcchInfoEntry* entry, u32 version) {
// convert ncchinfo if v3
if (version == 3) { // ncchinfo v3
u8* entry_data = (u8*) (entry);
memmove(entry_data + 56, entry_data + 48, 112);
memset(entry_data + 48, 0, 8); // zero out nonexistent title id
} else if (version != 4) { // !ncchinfo v4.0/v4.1/v4.2
return 1;
}
// poor man's UTF-16 -> UTF-8
if (entry->filename[1] == 0x00) {
for (u32 i = 1; i < (sizeof(entry->filename) / 2); i++)
entry->filename[i] = entry->filename[i*2];
}
// fix sdmc: prefix
if (memcmp(entry->filename, "sdmc:", 5) == 0)
memmove(entry->filename, entry->filename + 5, 112 - 5);
// workaround (1) for older (v4.0) ncchinfo.bin
// this combination means seed crypto rather than FixedKey
if ((entry->ncchFlag7 == 0x01) && entry->ncchFlag3)
entry->ncchFlag7 = 0x20;
// workaround (2) for older (v4.1) ncchinfo.bin
if (!entry->size_b) entry->size_b = entry->size_mb * 1024 * 1024;
return 0;
}
u32 BuildNcchInfoXorpad(u8* buffer, NcchInfoEntry* entry, u32 size, u32 offset) {
// set NCCH key
// build faux NCCH header from entry
NcchHeader ncch = { 0 };
memcpy(ncch.signature, entry->keyY, 16);
ncch.flags[3] = (u8) entry->ncchFlag3;
ncch.flags[7] = (u8) (entry->ncchFlag7 & ~0x04);
ncch.programId = ncch.partitionId = entry->titleId;
if (SetNcchKey(&ncch, 1) != 0)
return 1;
// write xorpad
memset(buffer, 0, size);
ctr_decrypt_byte(buffer, buffer, size, offset, AES_CNT_CTRNAND_MODE, entry->ctr);
return 0;
}

30
source/game/ncchinfo.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include "common.h"
#define NCCHINFO_NAME "ncchinfo.bin"
#define NCCHINFO_V3_MAGIC 0xF0000003
#define NCCHINFO_V4_MAGIC 0xF0000004
#define NCCHINFO_V3_SIZE 160
typedef struct {
u8 ctr[16];
u8 keyY[16];
u32 size_mb;
u32 size_b; // this is only used if it is non-zero
u32 ncchFlag7;
u32 ncchFlag3;
u64 titleId;
char filename[112];
} __attribute__((packed)) NcchInfoEntry;
typedef struct {
u32 padding;
u32 ncch_info_version;
u32 n_entries;
u8 reserved[4];
} __attribute__((packed, aligned(16))) NcchInfoHeader;
u32 GetNcchInfoVersion(NcchInfoHeader* info);
u32 FixNcchInfoEntry(NcchInfoEntry* entry, u32 version);
u32 BuildNcchInfoXorpad(u8* buffer, NcchInfoEntry* entry, u32 size, u32 offset);

View File

@ -12,6 +12,7 @@
#include "nand.h" #include "nand.h"
#include "virtual.h" #include "virtual.h"
#include "vcart.h" #include "vcart.h"
#include "ncchinfo.h"
#include "image.h" #include "image.h"
#define N_PANES 2 #define N_PANES 2
@ -577,6 +578,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
u32 filetype = IdentifyFileType(curr_entry->path); u32 filetype = IdentifyFileType(curr_entry->path);
u32 drvtype = DriveType(curr_entry->path); u32 drvtype = DriveType(curr_entry->path);
bool in_output_path = (strncmp(current_path, OUTPUT_PATH, 256) == 0);
// special stuff, only available for known filetypes (see int special below) // special stuff, only available for known filetypes (see int special below)
bool mountable = ((filetype & FTYPE_MOUNTABLE) && !(drvtype & DRV_IMAGE)); bool mountable = ((filetype & FTYPE_MOUNTABLE) && !(drvtype & DRV_IMAGE));
bool verificable = (filetype & FYTPE_VERIFICABLE); bool verificable = (filetype & FYTPE_VERIFICABLE);
@ -585,8 +588,9 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
bool buildable = (filetype & FTYPE_BUILDABLE); bool buildable = (filetype & FTYPE_BUILDABLE);
bool buildable_legit = (filetype & FTYPE_BUILDABLE_L); bool buildable_legit = (filetype & FTYPE_BUILDABLE_L);
bool restorable = (CheckA9lh() && (filetype & FTYPE_RESTORABLE) && !(drvtype & DRV_SYSNAND)); bool restorable = (CheckA9lh() && (filetype & FTYPE_RESTORABLE) && !(drvtype & DRV_SYSNAND));
bool xorpadable = (filetype & FTYPE_XORPAD);
bool special_opt = mountable || verificable || decryptable || decryptable_inplace || bool special_opt = mountable || verificable || decryptable || decryptable_inplace ||
buildable || buildable_legit || restorable; buildable || buildable_legit || restorable || xorpadable;
char pathstr[32 + 1]; char pathstr[32 + 1];
TruncateString(pathstr, curr_entry->path, 32, 8); TruncateString(pathstr, curr_entry->path, 32, 8);
@ -602,7 +606,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
int special = (special_opt) ? ++n_opt : -1; int special = (special_opt) ? ++n_opt : -1;
int hexviewer = ++n_opt; int hexviewer = ++n_opt;
int calcsha = ++n_opt; int calcsha = ++n_opt;
int copystd = (strncmp(current_path, OUTPUT_PATH, 256) != 0) ? ++n_opt : -1; int copystd = (!in_output_path) ? ++n_opt : -1;
int inject = ((clipboard->n_entries == 1) && int inject = ((clipboard->n_entries == 1) &&
(clipboard->entry[0].type == T_FILE) && (clipboard->entry[0].type == T_FILE) &&
(drvtype & DRV_FAT) && (drvtype & DRV_FAT) &&
@ -618,7 +622,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
(filetype == GAME_EXEFS) ? "Mount as EXEFS image" : (filetype == GAME_EXEFS) ? "Mount as EXEFS image" :
(filetype == GAME_ROMFS) ? "Mount as ROMFS image" : (filetype == GAME_ROMFS) ? "Mount as ROMFS image" :
(filetype == GAME_TMD) ? "TMD file options..." : (filetype == GAME_TMD) ? "TMD file options..." :
(filetype == SYS_FIRM) ? "FIRM image options..." : "???"; (filetype == SYS_FIRM) ? "FIRM image options..." :
(filetype == MISC_NINFO) ? "NCCHinfo options..." : "???";
optionstr[hexviewer-1] = "Show in Hexeditor"; optionstr[hexviewer-1] = "Show in Hexeditor";
optionstr[calcsha-1] = "Calculate SHA-256"; optionstr[calcsha-1] = "Calculate SHA-256";
if (copystd > 0) optionstr[copystd-1] = "Copy to " OUTPUT_PATH; if (copystd > 0) optionstr[copystd-1] = "Copy to " OUTPUT_PATH;
@ -691,6 +696,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
int build = (buildable) ? ++n_opt : -1; int build = (buildable) ? ++n_opt : -1;
int build_legit = (buildable_legit) ? ++n_opt : -1; int build_legit = (buildable_legit) ? ++n_opt : -1;
int verify = (verificable) ? ++n_opt : -1; int verify = (verificable) ? ++n_opt : -1;
int xorpad = (xorpadable) ? ++n_opt : -1;
int xorpad_inplace = (xorpadable) ? ++n_opt : -1;
if (mount > 0) optionstr[mount-1] = "Mount image to drive"; if (mount > 0) optionstr[mount-1] = "Mount image to drive";
if (restore > 0) optionstr[restore-1] = "Restore SysNAND (safe)"; if (restore > 0) optionstr[restore-1] = "Restore SysNAND (safe)";
if (decrypt > 0) optionstr[decrypt-1] = "Decrypt file (SD output)"; if (decrypt > 0) optionstr[decrypt-1] = "Decrypt file (SD output)";
@ -698,6 +705,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
if (build > 0) optionstr[build-1] = (build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)"; if (build > 0) optionstr[build-1] = (build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)";
if (build_legit > 0) optionstr[build_legit-1] = "Build CIA (legit)"; if (build_legit > 0) optionstr[build_legit-1] = "Build CIA (legit)";
if (verify > 0) optionstr[verify-1] = "Verify file"; if (verify > 0) optionstr[verify-1] = "Verify file";
if (xorpad > 0) optionstr[xorpad-1] = "Build XORpads (SD output)";
if (xorpad_inplace > 0) optionstr[xorpad_inplace-1] = "Build XORpads (inplace)";
// auto select when there is only one option // auto select when there is only one option
user_select = (n_opt > 1) ? (int) ShowSelectPrompt(n_opt, optionstr, pathstr) : n_opt; user_select = (n_opt > 1) ? (int) ShowSelectPrompt(n_opt, optionstr, pathstr) : n_opt;
@ -787,10 +796,12 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
n_success, n_marked, n_other, n_marked); n_success, n_marked, n_other, n_marked);
else ShowPrompt(false, "%lu/%lu CIAs built ok", n_success, n_marked); else ShowPrompt(false, "%lu/%lu CIAs built ok", n_success, n_marked);
if (n_success) ShowPrompt(false, "%lu files written to %s", n_success, OUTPUT_PATH); if (n_success) ShowPrompt(false, "%lu files written to %s", n_success, OUTPUT_PATH);
if (n_success && in_output_path) GetDirContents(current_dir, current_path);
} else { } else {
if (BuildCiaFromGameFile(curr_entry->path, force_legit) == 0) if (BuildCiaFromGameFile(curr_entry->path, force_legit) == 0) {
ShowPrompt(false, "%s\nCIA built to %s", pathstr, OUTPUT_PATH); ShowPrompt(false, "%s\nCIA built to %s", pathstr, OUTPUT_PATH);
else ShowPrompt(false, "%s\nCIA build failed", pathstr); if (in_output_path) GetDirContents(current_dir, current_path);
} else ShowPrompt(false, "%s\nCIA build failed", pathstr);
} }
return 0; return 0;
} else if (user_select == verify) { // -> verify game / nand file } else if (user_select == verify) { // -> verify game / nand file
@ -832,6 +843,21 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
} 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, ShowPrompt(false, "%s\nNAND restore %s", pathstr,
(SafeRestoreNandDump(curr_entry->path) == 0) ? "success" : "failed"); (SafeRestoreNandDump(curr_entry->path) == 0) ? "success" : "failed");
} else if ((user_select == xorpad) || (user_select == xorpad_inplace)) {
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,
(success) ? "success" : "failed",
(!success || inplace) ? "" : "\nOutput dir: " OUTPUT_PATH);
GetDirContents(current_dir, current_path);
for (; *cursor < current_dir->n_entries; (*cursor)++) {
curr_entry = &(current_dir->entry[*cursor]);
if (strncasecmp(curr_entry->name, NCCHINFO_NAME, 32) == 0) break;
}
if (*cursor >= current_dir->n_entries) {
*scroll = 0;
*cursor = 1;
}
} }
return 1; return 1;