forked from Mirror/GodMode9
Added NCCH padgen ability
... from ncchinfo.bin
This commit is contained in:
parent
8d55cf4a62
commit
d9dbf14f8b
@ -8,8 +8,12 @@ u32 IdentifyFileType(const char* path) {
|
||||
const u8 firm_magic[] = { FIRM_MAGIC };
|
||||
u8 header[0x200] __attribute__((aligned(32))); // minimum required size
|
||||
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) &&
|
||||
(getbe64(header + 0x108) == (u64) 0) && (fsize >= 0x8FC8000)) {
|
||||
return IMG_NAND; // NAND image
|
||||
@ -33,9 +37,9 @@ u32 IdentifyFileType(const char* path) {
|
||||
} else if (ValidateNcchHeader((NcchHeader*) (void*) header) == 0) {
|
||||
NcchHeader* ncch = (NcchHeader*) (void*) header;
|
||||
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) {
|
||||
return GAME_EXEFS; // ExeFS file
|
||||
return GAME_EXEFS; // ExeFS file (false positives possible)
|
||||
} else if (memcmp(header, romfs_magic, sizeof(romfs_magic)) == 0) {
|
||||
return GAME_ROMFS; // RomFS file (check could be better)
|
||||
} 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) {
|
||||
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;
|
||||
}
|
||||
|
@ -14,11 +14,14 @@
|
||||
|
||||
#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 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 FTYPE_BUILDABLE (GAME_NCSD|GAME_NCCH|GAME_TMD)
|
||||
#define FTYPE_BUILDABLE_L (FTYPE_BUILDABLE&(GAME_TMD))
|
||||
#define FTYPE_RESTORABLE (IMG_NAND)
|
||||
#define FTYPE_XORPAD (MISC_NINFO)
|
||||
|
||||
u32 IdentifyFileType(const char* path);
|
||||
|
@ -6,3 +6,4 @@
|
||||
#include "exefs.h"
|
||||
#include "romfs.h"
|
||||
#include "firm.h"
|
||||
#include "ncchinfo.h"
|
||||
|
@ -1128,3 +1128,51 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -6,3 +6,4 @@ u32 VerifyGameFile(const char* path);
|
||||
u32 CheckEncryptedGameFile(const char* path);
|
||||
u32 DecryptGameFile(const char* path, bool inplace);
|
||||
u32 BuildCiaFromGameFile(const char* path, bool force_legit);
|
||||
u32 BuildNcchInfoXorpads(const char* destdir, const char* path);
|
||||
|
@ -60,6 +60,7 @@ typedef struct {
|
||||
} __attribute__((packed, aligned(16))) NcchHeader;
|
||||
|
||||
u32 ValidateNcchHeader(NcchHeader* header);
|
||||
u32 SetNcchKey(NcchHeader* ncch, u32 keyid);
|
||||
u32 SetupNcchCrypto(NcchHeader* ncch);
|
||||
u32 DecryptNcch(u8* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs);
|
||||
u32 DecryptNcchSequential(u8* data, u32 offset, u32 size);
|
||||
|
59
source/game/ncchinfo.c
Normal file
59
source/game/ncchinfo.c
Normal 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
30
source/game/ncchinfo.h
Normal 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);
|
@ -12,6 +12,7 @@
|
||||
#include "nand.h"
|
||||
#include "virtual.h"
|
||||
#include "vcart.h"
|
||||
#include "ncchinfo.h"
|
||||
#include "image.h"
|
||||
|
||||
#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 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)
|
||||
bool mountable = ((filetype & FTYPE_MOUNTABLE) && !(drvtype & DRV_IMAGE));
|
||||
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_legit = (filetype & FTYPE_BUILDABLE_L);
|
||||
bool restorable = (CheckA9lh() && (filetype & FTYPE_RESTORABLE) && !(drvtype & DRV_SYSNAND));
|
||||
bool xorpadable = (filetype & FTYPE_XORPAD);
|
||||
bool special_opt = mountable || verificable || decryptable || decryptable_inplace ||
|
||||
buildable || buildable_legit || restorable;
|
||||
buildable || buildable_legit || restorable || xorpadable;
|
||||
|
||||
char pathstr[32 + 1];
|
||||
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 hexviewer = ++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) &&
|
||||
(clipboard->entry[0].type == T_FILE) &&
|
||||
(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_ROMFS) ? "Mount as ROMFS image" :
|
||||
(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[calcsha-1] = "Calculate SHA-256";
|
||||
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_legit = (buildable_legit) ? ++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 (restore > 0) optionstr[restore-1] = "Restore SysNAND (safe)";
|
||||
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_legit > 0) optionstr[build_legit-1] = "Build CIA (legit)";
|
||||
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
|
||||
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);
|
||||
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 && in_output_path) GetDirContents(current_dir, current_path);
|
||||
} 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);
|
||||
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;
|
||||
} 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)
|
||||
ShowPrompt(false, "%s\nNAND restore %s", pathstr,
|
||||
(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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user