Allow installation of game files

Should work for NCCH, NCSD, CIA, TMD from NUS/CDN and DSi eShop titles in NDS format
This commit is contained in:
d0k3 2020-07-22 00:20:16 +02:00
parent 8a7448995f
commit d2c47b7977
11 changed files with 703 additions and 76 deletions

View File

@ -50,6 +50,7 @@
#define FTYPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|BIN_KEYDB)) #define FTYPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|BIN_KEYDB))
#define FTYPE_CIABUILD(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_TMD)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW)))) #define FTYPE_CIABUILD(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_TMD)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW))))
#define FTYPE_CIABUILD_L(tp) (FTYPE_CIABUILD(tp) && (tp&(GAME_TMD))) #define FTYPE_CIABUILD_L(tp) (FTYPE_CIABUILD(tp) && (tp&(GAME_TMD)))
#define FTYPE_CIAINSTALL(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_CIA)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW))) || (tp&(GAME_TMD)&&(tp&(FLAG_NUSCDN))))
#define FTYPE_CXIDUMP(tp) (tp&(GAME_TMD)) #define FTYPE_CXIDUMP(tp) (tp&(GAME_TMD))
#define FTYPE_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB)) #define FTYPE_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB))
#define FTYPE_KEYBUILD(tp) (tp&(BIN_KEYDB|BIN_LEGKEY)) #define FTYPE_KEYBUILD(tp) (tp&(BIN_KEYDB|BIN_LEGKEY))

View File

@ -2,29 +2,9 @@
#include "common.h" #include "common.h"
#include "ticket.h" #include "ticket.h"
#include "tie.h"
// There's probably a better place to put this
#define SD_TITLEDB_PATH(emu) ((emu) ? "B:/dbs/title.db" : "A:/dbs/title.db")
// https://www.3dbrew.org/wiki/Inner_FAT // https://www.3dbrew.org/wiki/Inner_FAT
// https://www.3dbrew.org/wiki/Title_Database
typedef struct {
u64 title_size;
u32 title_type; // usually == 0x40
u32 title_version;
u8 flags_0[4];
u32 tmd_content_id;
u32 cmd_content_id;
u8 flags_1[4];
u32 extdata_id_low; // 0 if the title doesn't use extdata
u8 reserved1[4];
u8 flags_2[8];
char product_code[16];
u8 reserved2[16];
u8 unknown[4]; // appears to not matter what's here
u8 reserved3[44];
} __attribute__((packed)) TitleInfoEntry;
u32 GetNumTitleInfoEntries(const char* path); u32 GetNumTitleInfoEntries(const char* path);
u32 GetNumTickets(const char* path); u32 GetNumTickets(const char* path);

View File

@ -9,3 +9,42 @@ u32 CheckCmdSize(CmdHeader* cmd, u64 fsize) {
return (fsize == cmdsize) ? 0 : 1; return (fsize == cmdsize) ? 0 : 1;
} }
u32 BuildCmdData(CmdHeader* cmd, TitleMetaData* tmd) {
u32 content_count = getbe16(tmd->content_count);
// header basic info
cmd->cmd_id = 0x1;
cmd->n_entries = content_count;
cmd->n_cmacs = content_count;
cmd->unknown = 0x0; // this means no CMACs, only valid for NAND
// copy content ids
u32* cnt_id = (u32*) (cmd + 1);
u32* cnt_id_cpy = cnt_id + content_count;
TmdContentChunk* chunk = (TmdContentChunk*) (tmd + 1);
for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++, chunk++) {
cnt_id[i] = getbe32(chunk->id);
cnt_id_cpy[i] = cnt_id[i];
}
// bubble sort the second content id list
u32 b = 0;
while ((b < content_count) && (b < TMD_MAX_CONTENTS)) {
for (b = 1; (b < content_count) && (b < TMD_MAX_CONTENTS); b++) {
if (cnt_id_cpy[b] < cnt_id_cpy[b-1]) {
u32 swp = cnt_id_cpy[b];
cnt_id_cpy[b] = cnt_id_cpy[b-1];
cnt_id_cpy[b-1] = swp;
}
}
}
// set CMACs to 0xFF
u8* cnt_cmac = (u8*) (cnt_id + (2*cmd->n_entries));
memset(cmd->cmac, 0xFF, 0x10);
memset(cnt_cmac, 0xFF, 0x10 * content_count);
// we still need to fix / set the CMACs inside the CMD file!
return 0;
}

View File

@ -1,6 +1,10 @@
#pragma once #pragma once
#include "common.h" #include "common.h"
#include "tmd.h"
#define CMD_SIZE_N(n) (sizeof(CmdHeader) + ((n)*(sizeof(u32)+sizeof(u32)+0x10)))
#define CMD_SIZE_NS(n) (sizeof(CmdHeader) + ((n)*(sizeof(u32)+sizeof(u32))))
// from: http://3dbrew.org/wiki/Titles#Data_Structure // from: http://3dbrew.org/wiki/Titles#Data_Structure
@ -16,3 +20,4 @@ typedef struct {
} __attribute__((packed, aligned(4))) CmdHeader; } __attribute__((packed, aligned(4))) CmdHeader;
u32 CheckCmdSize(CmdHeader* cmd, u64 fsize); u32 CheckCmdSize(CmdHeader* cmd, u64 fsize);
u32 BuildCmdData(CmdHeader* cmd, TitleMetaData* tmd);

View File

@ -14,6 +14,9 @@
#include "tad.h" #include "tad.h"
#include "3dsx.h" #include "3dsx.h"
#include "tmd.h" #include "tmd.h"
#include "ticket.h"
#include "tie.h"
#include "cmd.h" #include "cmd.h"
#include "bdri.h"
#include "ticketdb.h" #include "ticketdb.h"
#include "ncchinfo.h" #include "ncchinfo.h"

92
arm9/source/game/tie.c Normal file
View File

@ -0,0 +1,92 @@
#include "tie.h"
#include "cmd.h"
#define CMD_SIZE_ALIGN(sd) (sd ? 0x8000 : 0x4000)
u32 BuildTitleInfoEntryTmd(TitleInfoEntry* tie, TitleMetaData* tmd, bool sd) {
u64 title_id = getbe64(tmd->title_id);
u32 has_id1 = false;
// set basic values
memset(tie, 0x00, sizeof(TitleInfoEntry));
tie->title_type = 0x40;
// title version, product code, cmd id
tie->title_version = getbe16(tmd->title_version);
tie->cmd_content_id = 0x01;
memcpy(tie->unknown, "GM9", 4); // GM9 install magic number
// calculate base title size
// align size: 0x4000 for TWL and CTRNAND, 0x8000 for SD
u32 align_size = CMD_SIZE_ALIGN(sd);
u32 content_count = getbe16(tmd->content_count);
TmdContentChunk* chunk = (TmdContentChunk*) (tmd + 1);
tie->title_size =
(align_size * 3) + // base folder + 'content' + 'cmd'
align(TMD_SIZE_N(content_count), align_size) + // TMD
align(CMD_SIZE_N(content_count), align_size); // CMD
for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++, chunk++) {
if (getbe32(chunk->id) == 1) has_id1 = true; // will be useful later
tie->title_size += align(getbe64(chunk->size), align_size);
}
// manual? (we need to properly check this later)
if (has_id1 && (((title_id >> 32) == 0x00040000) || ((title_id >> 32) == 0x00040010))) {
tie->flags_0[0] = 0x1; // this may have a manual
}
return 0;
}
u32 BuildTitleInfoEntryTwl(TitleInfoEntry* tie, TitleMetaData* tmd, TwlHeader* twl) {
u64 title_id = getbe64(tmd->title_id);
if (ValidateTwlHeader(twl) != 0) return 1;
if (BuildTitleInfoEntryTmd(tie, tmd, false) != 0) return 1;
// product code
memcpy(tie->product_code, twl->game_title, 0x0A);
// specific flags
// see: http://3dbrew.org/wiki/Titles
if ((title_id >> 32) == 0x00048004) { // TWL app / game
tie->flags_2[0] = 0x01;
tie->flags_2[4] = 0x01;
tie->flags_2[5] = 0x01;
}
return 0;
}
u32 BuildTitleInfoEntryNcch(TitleInfoEntry* tie, TitleMetaData* tmd, NcchHeader* ncch, NcchExtHeader* exthdr, bool sd) {
u64 title_id = getbe64(tmd->title_id);
if (ValidateNcchHeader(ncch) != 0) return 1;
if (BuildTitleInfoEntryTmd(tie, tmd, sd) != 0) return 1;
// product code, extended title version
memcpy(tie->product_code, ncch->productcode, 0x10);
tie->title_version |= (ncch->version << 16);
// specific flags
// see: http://3dbrew.org/wiki/Titles
if (!((title_id >> 32) & 0x10)) // not a system title
tie->flags_2[4] = 0x01;
// stuff from extheader
if (exthdr) {
// add save data size to title size
if (exthdr->savedata_size) {
u32 align_size = CMD_SIZE_ALIGN(sd);
tie->title_size +=
align_size + // 'data' folder
align(exthdr->savedata_size, align_size); // savegame
tie->flags_1[0] = 0x01; // has SD save
};
// extdata ID low (hacky)
tie->extdata_id_low = getle32(exthdr->aci_data + 0x30 - 0x0C + 0x04);
} else tie->flags_0[0] = 0x00; // no manual
return 0;
}

31
arm9/source/game/tie.h Normal file
View File

@ -0,0 +1,31 @@
#pragma once
#include "common.h"
#include "tmd.h"
#include "ncch.h"
#include "nds.h"
// There's probably a better place to put this
#define SD_TITLEDB_PATH(emu) ((emu) ? "B:/dbs/title.db" : "A:/dbs/title.db")
// see: https://www.3dbrew.org/wiki/Title_Database
typedef struct {
u64 title_size;
u32 title_type; // usually == 0x40
u32 title_version;
u8 flags_0[4];
u32 tmd_content_id;
u32 cmd_content_id;
u8 flags_1[4];
u32 extdata_id_low; // 0 if the title doesn't use extdata
u8 reserved1[4];
u8 flags_2[8];
char product_code[16];
u8 reserved2[16];
u8 unknown[4]; // appears to not matter what's here
u8 reserved3[44];
} __attribute__((packed)) TitleInfoEntry;
u32 BuildTitleInfoEntryTwl(TitleInfoEntry* tie, TitleMetaData* tmd, TwlHeader* twl);
u32 BuildTitleInfoEntryNcch(TitleInfoEntry* tie, TitleMetaData* tmd, NcchHeader* ncch, NcchExtHeader* exthdr, bool sd);

View File

@ -1091,9 +1091,12 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
bool cryptable_inplace = ((encryptable||decryptable) && !in_output_path && (drvtype & DRV_FAT)); bool cryptable_inplace = ((encryptable||decryptable) && !in_output_path && (drvtype & DRV_FAT));
bool cia_buildable = (FTYPE_CIABUILD(filetype)); bool cia_buildable = (FTYPE_CIABUILD(filetype));
bool cia_buildable_legit = (FTYPE_CIABUILD_L(filetype)); bool cia_buildable_legit = (FTYPE_CIABUILD_L(filetype));
bool cia_installable = (FTYPE_CIAINSTALL(filetype)) && !(drvtype & DRV_CTRNAND) &&
!(drvtype & DRV_TWLNAND) && !(drvtype & DRV_ALIAS);
bool cxi_dumpable = (FTYPE_CXIDUMP(filetype)); bool cxi_dumpable = (FTYPE_CXIDUMP(filetype));
bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path; bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path;
bool key_buildable = (FTYPE_KEYBUILD(filetype)) && !in_output_path && !((drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND)); bool key_buildable = (FTYPE_KEYBUILD(filetype)) && !in_output_path &&
!((drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND));
bool titleinfo = (FTYPE_TITLEINFO(filetype)); bool titleinfo = (FTYPE_TITLEINFO(filetype));
bool ciacheckable = (FTYPE_CIACHECK(filetype)); bool ciacheckable = (FTYPE_CIACHECK(filetype));
bool renamable = (FTYPE_RENAMABLE(filetype)); bool renamable = (FTYPE_RENAMABLE(filetype));
@ -1308,6 +1311,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
int cia_build = (cia_buildable) ? ++n_opt : -1; int cia_build = (cia_buildable) ? ++n_opt : -1;
int cia_build_legit = (cia_buildable_legit) ? ++n_opt : -1; int cia_build_legit = (cia_buildable_legit) ? ++n_opt : -1;
int cxi_dump = (cxi_dumpable) ? ++n_opt : -1; int cxi_dump = (cxi_dumpable) ? ++n_opt : -1;
int cia_install = (cia_installable) ? ++n_opt : -1;
int tik_build_enc = (tik_buildable) ? ++n_opt : -1; int tik_build_enc = (tik_buildable) ? ++n_opt : -1;
int tik_build_dec = (tik_buildable) ? ++n_opt : -1; int tik_build_dec = (tik_buildable) ? ++n_opt : -1;
int key_build = (key_buildable) ? ++n_opt : -1; int key_build = (key_buildable) ? ++n_opt : -1;
@ -1340,6 +1344,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
if (cia_build > 0) optionstr[cia_build-1] = (cia_build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)"; if (cia_build > 0) optionstr[cia_build-1] = (cia_build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)";
if (cia_build_legit > 0) optionstr[cia_build_legit-1] = "Build CIA (legit)"; if (cia_build_legit > 0) optionstr[cia_build_legit-1] = "Build CIA (legit)";
if (cxi_dump > 0) optionstr[cxi_dump-1] = "Dump CXI/NDS file"; if (cxi_dump > 0) optionstr[cxi_dump-1] = "Dump CXI/NDS file";
if (cia_install > 0) optionstr[cia_install-1] = "Install game file";
if (tik_build_enc > 0) optionstr[tik_build_enc-1] = "Build " TIKDB_NAME_ENC; if (tik_build_enc > 0) optionstr[tik_build_enc-1] = "Build " TIKDB_NAME_ENC;
if (tik_build_dec > 0) optionstr[tik_build_dec-1] = "Build " TIKDB_NAME_DEC; if (tik_build_dec > 0) optionstr[tik_build_dec-1] = "Build " TIKDB_NAME_DEC;
if (key_build > 0) optionstr[key_build-1] = "Build " KEYDB_NAME; if (key_build > 0) optionstr[key_build-1] = "Build " KEYDB_NAME;
@ -1546,6 +1551,53 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
} }
return 0; return 0;
} }
else if (user_select == cia_install) { // -> install game file
bool to_emunand = false;
if (CheckVirtualDrive("E:")) {
optionstr[0] = "Install to SysNAND";
optionstr[1] = "Install to EmuNAND";
user_select = (int) ShowSelectPrompt(2, optionstr, (n_marked > 1) ?
"%s\n%(%lu files selected)" : "%s", pathstr, n_marked);
if (!user_select) return 0;
else to_emunand = (user_select == 2);
}
if ((n_marked > 1) && ShowPrompt(true, "Try to install all %lu selected files?", n_marked)) {
u32 n_success = 0;
u32 n_other = 0;
ShowString("Trying to install %lu files...", n_marked);
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 & TYPE_BASE)) {
n_other++;
continue;
}
DrawDirContents(current_dir, (*cursor = i), scroll);
if (InstallGameFile(path, to_emunand, false)) n_success++;
else { // on failure: show error, continue
char lpathstr[32+1];
TruncateString(lpathstr, path, 32, 8);
if (ShowPrompt(true, "%s\nInstall failed\n \nContinue?", lpathstr)) continue;
else break;
}
current_dir->entry[i].marked = false;
}
if (n_other) {
ShowPrompt(false, "%lu/%lu files installed ok\n%lu/%lu not of same type",
n_success, n_marked, n_other, n_marked);
} else ShowPrompt(false, "%lu/%lu files installed ok", n_success, n_marked);
} else {
u32 ret = InstallGameFile(file_path, to_emunand, false);
ShowPrompt(false, "%s\nInstall %s", pathstr, (ret == 0) ? "success" : "failed");
if ((ret != 0) && (filetype & (GAME_NCCH|GAME_NCSD)) &&
ShowPrompt(true, "%s\nfile failed install.\n \nVerify now?", pathstr)) {
ShowPrompt(false, "%s\nVerification %s", pathstr,
(VerifyGameFile(file_path) == 0) ? "success" : "failed");
}
}
return 0;
}
else if (user_select == verify) { // -> verify game / nand file else if (user_select == verify) { // -> verify game / nand file
if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) { if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) {
u32 n_success = 0; u32 n_success = 0;

View File

@ -1,4 +1,5 @@
#include "gameutil.h" #include "gameutil.h"
#include "nandcmac.h"
#include "disadiff.h" #include "disadiff.h"
#include "game.h" #include "game.h"
#include "nand.h" // so that we can trim NAND images #include "nand.h" // so that we can trim NAND images
@ -1228,6 +1229,269 @@ u32 CryptGameFile(const char* path, bool inplace, bool encrypt) {
return ret; return ret;
} }
u32 GetInstallAppPath(char* path, const char* drv, const u8* title_id, const u8* content_id) {
const u8 dlc_tid_high[] = { DLC_TID_HIGH };
bool dlc = (memcmp(title_id, dlc_tid_high, sizeof(dlc_tid_high)) == 0);
u32 tid_high = getbe32(title_id);
u32 tid_low = getbe32(title_id + 4);
if ((*drv == '2') || (*drv == '5')) // TWL titles need TWL title ID
tid_high = 0x00030000 | (tid_high&0xFF);
if (!content_id) { // just the base title path in that case
snprintf(path, 256, "%2.2s/title/%08lx/%08lx",
drv, tid_high, tid_low);
} else { // full app path
snprintf(path, 256, "%2.2s/title/%08lx/%08lx/content/%s%08lx.app",
drv, tid_high, tid_low, dlc ? "00000000/" : "", getbe32(content_id));
}
return 0;
}
u32 InstallCiaContent(const char* drv, const char* path_content, u32 offset, u32 size,
TmdContentChunk* chunk, const u8* title_id, const u8* titlekey, bool cxi_fix) {
char dest[256];
// create destination path and ensure it exists
GetInstallAppPath(dest, drv, title_id, chunk->id);
fvx_rmkpath(dest);
// open file(s)
FIL ofile;
FIL dfile;
FSIZE_t fsize;
UINT bytes_read, bytes_written;
if (fvx_open(&ofile, path_content, FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 1;
fvx_lseek(&ofile, offset);
fsize = fvx_size(&ofile);
if (offset > fsize) return 1;
if (!size) size = fsize - offset;
if (fvx_open(&dfile, dest, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK) {
fvx_close(&ofile);
return 1;
}
// ensure free space for destination file
if ((fvx_lseek(&dfile, size) != FR_OK) ||
(fvx_tell(&dfile) != size) ||
(fvx_lseek(&dfile, 0) != FR_OK)) {
fvx_close(&ofile);
fvx_close(&dfile);
fvx_unlink(dest);
return 1;
}
// allocate buffer
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
if (!buffer) {
fvx_close(&ofile);
fvx_close(&dfile);
fvx_unlink(dest);
return 1;
}
// main loop starts here
u8 ctr_in[16];
u8 ctr_out[16];
u32 ret = 0;
bool cia_crypto = getbe16(chunk->type) & 0x1;
GetTmdCtr(ctr_in, chunk);
GetTmdCtr(ctr_out, chunk);
if (!ShowProgress(0, 0, path_content)) ret = 1;
for (u32 i = 0; (i < size) && (ret == 0); i += STD_BUFFER_SIZE) {
u32 read_bytes = min(STD_BUFFER_SIZE, (size - i));
if (fvx_read(&ofile, buffer, read_bytes, &bytes_read) != FR_OK) ret = 1;
if (cia_crypto && (DecryptCiaContentSequential(buffer, read_bytes, ctr_in, titlekey) != 0)) ret = 1;
if ((i == 0) && cxi_fix && (SetNcchSdFlag(buffer) != 0)) ret = 1;
if (i == 0) sha_init(SHA256_MODE);
sha_update(buffer, read_bytes);
if (fvx_write(&dfile, 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, path_content)) ret = 1;
}
u8 hash[0x20];
sha_get(hash);
free(buffer);
fvx_close(&ofile);
fvx_close(&dfile);
// did something go wrong?
if (ret != 0) fvx_unlink(dest);
// chunk size / chunk hash
for (u32 i = 0; i < 8; i++) chunk->size[i] = (u8) (size >> (8*(7-i)));
memcpy(chunk->hash, hash, 0x20);
return ret;
}
u32 InstallCiaSystemData(CiaStub* cia, const char* drv) {
// this assumes contents already installed(!)
// we use hardcoded IDs for CMD (0x1), TMD (0x0), save (0x1/0x0)
TitleInfoEntry tie;
CmdHeader* cmd;
TicketCommon* ticket = &(cia->ticket);
TitleMetaData* tmd = &(cia->tmd);
TmdContentChunk* content_list = cia->content_list;
bool syscmd = ((*drv == '1') || (*drv == '4'));
bool sdtie = ((*drv == 'A') || (*drv == 'B'));
u32 content_count = getbe16(tmd->content_count);
u8* title_id = ticket->title_id;
u32 tid_high = getbe32(title_id);
u32 tid_low = getbe32(title_id + 4);
char path_titledb[32];
char path_ticketdb[32];
char path_tmd[64];
char path_cmd[64];
char path_save[128];
// sanity checks
if (content_count == 0) return 1;
if ((*drv != '1') && (*drv != '2') && (*drv != 'A') &&
(*drv != '4') && (*drv != '5') && (*drv != 'B'))
return 1;
// TWL titles need TWL title ID high
if ((*drv == '2') || (*drv == '5'))
tid_high = 0x00030000 | (tid_high&0xFF);
// progress update
if (!ShowProgress(0, 0, "TMD/CMD/TiE/Ticket/Save")) return 1;
// collect data for title info entry
char path_cnt0[256];
u8 hdr_cnt0[0x600]; // we don't need more
NcchHeader* ncch = NULL;
NcchExtHeader* exthdr = NULL;
GetInstallAppPath(path_cnt0, drv, title_id, content_list->id);
if (fvx_qread(path_cnt0, hdr_cnt0, 0, 0x600, NULL) != FR_OK)
return 1;
if (ValidateNcchHeader((void*) hdr_cnt0) == 0) {
ncch = (void*) hdr_cnt0;
exthdr = (void*) (hdr_cnt0 + sizeof(NcchHeader));
if (!(ncch->size_exthdr) ||
(DecryptNcch((u8*) exthdr, NCCH_EXTHDR_OFFSET, 0x400, ncch, NULL) != 0))
exthdr = NULL;
}
// build title info entry
if ((ncch && (BuildTitleInfoEntryNcch(&tie, tmd, ncch, exthdr, sdtie) != 0)) ||
(!ncch && (BuildTitleInfoEntryTwl(&tie, tmd, (TwlHeader*) (void*) hdr_cnt0) != 0)))
return 1;
// build the cmd
cmd = (CmdHeader*) malloc(CMD_SIZE_N(content_count));
if (!cmd) return 1;
BuildCmdData(cmd, tmd);
if (!syscmd) cmd->unknown = 0xFFFFFFFE; // mark this as custom built
// generate all the paths
snprintf(path_titledb, 32, "%2.2s/dbs/title.db",
(*drv == '2') ? "1:" : *drv == '5' ? "4:" : drv);
snprintf(path_ticketdb, 32, "%2.2s/dbs/ticket.db",
((*drv == 'A') || (*drv == '2')) ? "1:" :
((*drv == 'B') || (*drv == '5')) ? "4:" : drv);
snprintf(path_tmd, 64, "%2.2s/title/%08lx/%08lx/content/00000000.tmd",
drv, tid_high, tid_low);
snprintf(path_cmd, 64, "%2.2s/title/%08lx/%08lx/content/cmd/00000001.cmd",
drv, tid_high, tid_low);
// progress update
if (!ShowProgress(1, 5, "TMD/CMD/TiE/Ticket/Save")) return 1;
// copy tmd & cmd
fvx_rmkpath(path_tmd);
fvx_rmkpath(path_cmd);
if ((fvx_qwrite(path_tmd, tmd, 0, TMD_SIZE_N(content_count), NULL) != FR_OK) ||
(fvx_qwrite(path_cmd, cmd, 0,
syscmd ? CMD_SIZE_NS(content_count) : CMD_SIZE_N(content_count), NULL) != FR_OK)) {
free(cmd);
return 1;
}
free(cmd); // we don't need this anymore
// progress update
if (!ShowProgress(2, 5, "TMD/CMD/TiE/Ticket/Save")) return 1;
// generate savedata
if (exthdr && (exthdr->savedata_size)) {
// generate the save path (thanks ihaveamac for system path)
if ((*drv == '1') || (*drv == '4')) { // ooof, system save
u8 sd_keyy[16] __attribute__((aligned(4)));
char path_movable[32];
u32 sha256sum[8];
snprintf(path_movable, 32, "%2.2s/private/movable.sed", drv);
if (fvx_qread(path_movable, sd_keyy, 0x110, 0x10, NULL) != FR_OK) return 1;
memset(sd_keyy, 0x00, 16);
sha_quick(sha256sum, sd_keyy, 0x10, SHA256_MODE);
snprintf(path_save, 128, "%2.2s/data/%08lx%08lx%08lx%08lx/sysdata/%08lx/00000000",
drv, sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3],
tid_low | 0x00020000);
} else { // SD save, simple
snprintf(path_save, 128, "%2.2s/title/%08lx/%08lx/data/00000001.sav",
drv, tid_high, tid_low);
}
// generate the save file
u8 zeroes[0x20] = { 0x00 };
UINT bw;
FIL save;
fvx_rmkpath(path_save);
if (fvx_open(&save, path_save, FA_WRITE | FA_CREATE_NEW) != FR_OK)
return 1;
if ((fvx_write(&save, zeroes, 0x20, &bw) != FR_OK) || (bw != 0x20))
bw = 0;
fvx_lseek(&save, exthdr->savedata_size);
fvx_sync(&save);
fvx_close(&save);
if (bw != 0x20) return 1;
}
// progress update
if (!ShowProgress(3, 5, "TMD/CMD/TiE/Ticket/Save")) return 1;
// write ticket and title databases
// ensure remounting the old mount path
char path_store[256] = { 0 };
char* path_bak = NULL;
strncpy(path_store, GetMountPath(), 256);
if (*path_store) path_bak = path_store;
// ticket database
if (!InitImgFS(path_ticketdb) ||
((AddTicketToDB("D:/partitionA.bin", title_id, (Ticket*) ticket, true)) != 0)) {
InitImgFS(path_bak);
return 1;
}
// progress update
if (!ShowProgress(4, 5, "TMD/CMD/TiE/Ticket/Save")) return 1;
// title database
if (!InitImgFS(path_titledb) ||
((AddTitleInfoEntryToDB("D:/partitionA.bin", title_id, &tie, true)) != 0)) {
InitImgFS(path_bak);
return 1;
}
// progress update
if (!ShowProgress(5, 5, "TMD/CMD/TiE/Ticket/Save")) return 1;
// restore old mount path
InitImgFS(path_bak);
// fix CMACs where required
if (!syscmd) FixFileCmac(path_cmd);
FixFileCmac(path_ticketdb);
FixFileCmac(path_titledb);
return 0;
}
u32 InsertCiaContent(const char* path_cia, const char* path_content, u32 offset, u32 size, u32 InsertCiaContent(const char* path_cia, const char* path_content, u32 offset, u32 size,
TmdContentChunk* chunk, const u8* titlekey, bool force_legit, bool cxi_fix, bool cdn_decrypt) { TmdContentChunk* chunk, const u8* titlekey, bool force_legit, bool cxi_fix, bool cdn_decrypt) {
// crypto types / ctr // crypto types / ctr
@ -1330,7 +1594,53 @@ u32 InsertCiaMeta(const char* path_cia, CiaMeta* meta) {
return (res) ? 0 : 1; return (res) ? 0 : 1;
} }
u32 BuildCiaFromTmdFileBuffered(const char* path_tmd, const char* path_cia, bool force_legit, bool cdn, void* buffer) { u32 InstallFromCiaFile(const char* path_cia, const char* path_dest) {
CiaInfo info;
u8 titlekey[16];
// start operation
if (!ShowProgress(0, 0, path_cia)) return 1;
// load CIA stub from origin
CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub));
if (!cia) return 1;
if ((LoadCiaStub(cia, path_cia) != 0) ||
(GetCiaInfo(&info, &(cia->header)) != 0) ||
(GetTitleKey(titlekey, (Ticket*)&(cia->ticket)) != 0)) {
free(cia);
return 1;
}
// install CIA contents
u8* title_id = cia->tmd.title_id;
u32 content_count = getbe16(cia->tmd.content_count);
u64 next_offset = info.offset_content;
u8* cnt_index = cia->header.content_index;
for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++) {
TmdContentChunk* chunk = &(cia->content_list[i]);
u64 size = getbe64(chunk->size);
u16 index = getbe16(chunk->index);
if (!(cnt_index[index/8] & (1 << (7-(index%8))))) continue; // don't try to install missing contents
if (InstallCiaContent(path_dest, path_cia, next_offset, size,
chunk, title_id, titlekey, false) != 0) {
free(cia);
return 1;
}
next_offset += size;
}
// fix TMD hashes, install CIA system data
if ((FixTmdHashes(&(cia->tmd)) != 0) ||
(InstallCiaSystemData(cia, path_dest) != 0)) {
free(cia);
return 1;
}
free(cia);
return 0;
}
u32 BuildInstallFromTmdFileBuffered(const char* path_tmd, const char* path_dest, bool force_legit, bool cdn, void* buffer, bool install) {
const u8 dlc_tid_high[] = { DLC_TID_HIGH }; const u8 dlc_tid_high[] = { DLC_TID_HIGH };
CiaStub* cia = (CiaStub*) buffer; CiaStub* cia = (CiaStub*) buffer;
@ -1456,58 +1766,78 @@ u32 BuildCiaFromTmdFileBuffered(const char* path_tmd, const char* path_cia, bool
} }
} }
// insert contents // insert / install contents
u8 titlekey[16] = { 0xFF }; u8 titlekey[16] = { 0xFF };
if ((GetTitleKey(titlekey, (Ticket*)&(cia->ticket)) != 0) && force_legit) return 1; if ((GetTitleKey(titlekey, (Ticket*)&(cia->ticket)) != 0) && force_legit) return 1;
if (WriteCiaStub(cia, path_cia) != 0) return 1; if (!install && (WriteCiaStub(cia, path_dest) != 0)) return 1;
for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++) { for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++) {
TmdContentChunk* chunk = &(content_list[i]); TmdContentChunk* chunk = &(content_list[i]);
if (present[i / 8] & (1 << (i % 8))) { if (present[i / 8] & (1 << (i % 8))) {
snprintf(name_content, 256 - (name_content - path_content), snprintf(name_content, 256 - (name_content - path_content),
(cdn) ? "%08lx" : (dlc && !cdn) ? "00000000/%08lx.app" : "%08lx.app", getbe32(chunk->id)); (cdn) ? "%08lx" : (dlc && !cdn) ? "00000000/%08lx.app" : "%08lx.app", getbe32(chunk->id));
if (InsertCiaContent(path_cia, path_content, 0, (u32) getbe64(chunk->size), chunk, titlekey, force_legit, false, cdn) != 0) { if (!install && (InsertCiaContent(path_dest, path_content, 0, (u32) getbe64(chunk->size),
chunk, titlekey, force_legit, false, cdn) != 0)) {
ShowPrompt(false, "ID %016llX.%08lX\nInsert content failed", getbe64(title_id), getbe32(chunk->id)); ShowPrompt(false, "ID %016llX.%08lX\nInsert content failed", getbe64(title_id), getbe32(chunk->id));
return 1; return 1;
} }
if (install && (InstallCiaContent(path_dest, path_content, 0, (u32) getbe64(chunk->size),
chunk, title_id, titlekey, false) != 0)) {
ShowPrompt(false, "ID %016llX.%08lX\nInstall content failed", getbe64(title_id), getbe32(chunk->id));
return 1;
}
} }
} }
// try to build & insert meta, but ignore result // try to build & insert meta, but ignore result
if (!install) {
CiaMeta* meta = (CiaMeta*) malloc(sizeof(CiaMeta)); CiaMeta* meta = (CiaMeta*) malloc(sizeof(CiaMeta));
if (meta) { if (meta) {
if (content_count && cdn) { if (content_count && cdn) {
if (!force_legit || !(getbe16(content_list->type) & 0x01)) { if (!force_legit || !(getbe16(content_list->type) & 0x01)) {
CiaInfo info; CiaInfo info;
GetCiaInfo(&info, &(cia->header)); GetCiaInfo(&info, &(cia->header));
if ((LoadNcchMeta(meta, path_cia, info.offset_content) == 0) && (InsertCiaMeta(path_cia, meta) == 0)) if ((LoadNcchMeta(meta, path_dest, info.offset_content) == 0) && (InsertCiaMeta(path_dest, meta) == 0))
cia->header.size_meta = CIA_META_SIZE; cia->header.size_meta = CIA_META_SIZE;
} }
} else if (content_count) { } else if (content_count) {
snprintf(name_content, 256 - (name_content - path_content), "%08lx.app", getbe32(content_list->id)); snprintf(name_content, 256 - (name_content - path_content), "%08lx.app", getbe32(content_list->id));
if ((LoadNcchMeta(meta, path_content, 0) == 0) && (InsertCiaMeta(path_cia, meta) == 0)) if ((LoadNcchMeta(meta, path_content, 0) == 0) && (InsertCiaMeta(path_dest, meta) == 0))
cia->header.size_meta = CIA_META_SIZE; cia->header.size_meta = CIA_META_SIZE;
} }
free(meta); free(meta);
} }
}
// write the CIA stub (take #2) // write the CIA stub (take #2)
if ((FixTmdHashes(tmd) != 0) || (WriteCiaStub(cia, path_cia) != 0)) if ((FixTmdHashes(tmd) != 0) ||
(install && (WriteCiaStub(cia, path_dest) != 0)) ||
(!install && (InstallCiaSystemData(cia, path_dest) != 0)))
return 1; return 1;
return 0; return 0;
} }
u32 BuildCiaFromTmdFile(const char* path_tmd, const char* path_cia, bool force_legit, bool cdn) { u32 InstallFromTmdFile(const char* path_tmd, const char* path_dest) {
void* buffer = (void*) malloc(sizeof(CiaStub)); void* buffer = (void*) malloc(sizeof(CiaStub));
if (!buffer) return 1; if (!buffer) return 1;
u32 ret = BuildCiaFromTmdFileBuffered(path_tmd, path_cia, force_legit, cdn, buffer); u32 ret = BuildInstallFromTmdFileBuffered(path_tmd, path_dest, false, true, buffer, true);
free(buffer); free(buffer);
return ret; return ret;
} }
u32 BuildCiaFromNcchFile(const char* path_ncch, const char* path_cia) { u32 BuildCiaFromTmdFile(const char* path_tmd, const char* path_dest, bool force_legit, bool cdn) {
void* buffer = (void*) malloc(sizeof(CiaStub));
if (!buffer) return 1;
u32 ret = BuildInstallFromTmdFileBuffered(path_tmd, path_dest, force_legit, cdn, buffer, true);
free(buffer);
return ret;
}
u32 BuildInstallFromNcchFile(const char* path_ncch, const char* path_dest, bool install) {
NcchExtHeader exthdr; NcchExtHeader exthdr;
NcchHeader ncch; NcchHeader ncch;
u8 title_id[8]; u8 title_id[8];
@ -1536,32 +1866,36 @@ u32 BuildCiaFromNcchFile(const char* path_ncch, const char* path_cia) {
(BuildFakeTicket((Ticket*)&(cia->ticket), title_id) != 0) || (BuildFakeTicket((Ticket*)&(cia->ticket), title_id) != 0) ||
(BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, 0)) || (BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, 0)) ||
(FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) ||
(WriteCiaStub(cia, path_cia) != 0)) { (!install && (WriteCiaStub(cia, path_dest) != 0))) {
free(cia); free(cia);
return 1; return 1;
} }
// insert NCCH content // insert / install NCCH content
TmdContentChunk* chunk = cia->content_list; TmdContentChunk* chunk = cia->content_list;
memset(chunk, 0, sizeof(TmdContentChunk)); // nothing else to do memset(chunk, 0, sizeof(TmdContentChunk)); // nothing else to do
if (InsertCiaContent(path_cia, path_ncch, 0, 0, chunk, NULL, false, true, false) != 0) { if ((!install && (InsertCiaContent(path_dest, path_ncch, 0, 0, chunk, NULL, false, true, false) != 0)) ||
(install && (InstallCiaContent(path_dest, path_ncch, 0, 0, chunk, title_id, NULL, true) != 0))) {
free(cia); free(cia);
return 1; return 1;
} }
// optional stuff (proper titlekey / meta data) // optional stuff (proper titlekey / meta data)
if (!install) {
CiaMeta* meta = (CiaMeta*) malloc(sizeof(CiaMeta)); CiaMeta* meta = (CiaMeta*) malloc(sizeof(CiaMeta));
if (meta && has_exthdr && (BuildCiaMeta(meta, &exthdr, NULL) == 0) && if (meta && has_exthdr && (BuildCiaMeta(meta, &exthdr, NULL) == 0) &&
(LoadExeFsFile(meta->smdh, path_ncch, 0, "icon", sizeof(meta->smdh), NULL) == 0) && (LoadExeFsFile(meta->smdh, path_ncch, 0, "icon", sizeof(meta->smdh), NULL) == 0) &&
(InsertCiaMeta(path_cia, meta) == 0)) (InsertCiaMeta(path_dest, meta) == 0))
cia->header.size_meta = CIA_META_SIZE; cia->header.size_meta = CIA_META_SIZE;
free(meta); free(meta);
}
// write the CIA stub (take #2) // write the CIA stub (take #2)
FindTitleKey((Ticket*)(&cia->ticket), title_id); FindTitleKey((Ticket*)(&cia->ticket), title_id);
if ((FixTmdHashes(&(cia->tmd)) != 0) || if ((FixTmdHashes(&(cia->tmd)) != 0) ||
(FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) ||
(WriteCiaStub(cia, path_cia) != 0)) { (!install && (WriteCiaStub(cia, path_dest) != 0)) ||
(install && (InstallCiaSystemData(cia, path_dest) != 0))) {
free(cia); free(cia);
return 1; return 1;
} }
@ -1570,7 +1904,7 @@ u32 BuildCiaFromNcchFile(const char* path_ncch, const char* path_cia) {
return 0; return 0;
} }
u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) { u32 BuildInstallFromNcsdFile(const char* path_ncsd, const char* path_dest, bool install) {
NcchExtHeader exthdr; NcchExtHeader exthdr;
NcsdHeader ncsd; NcsdHeader ncsd;
NcchHeader ncch; NcchHeader ncch;
@ -1602,33 +1936,39 @@ u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) {
(BuildFakeTicket((Ticket*)&(cia->ticket), title_id) != 0) || (BuildFakeTicket((Ticket*)&(cia->ticket), title_id) != 0) ||
(BuildFakeTmd(&(cia->tmd), title_id, content_count, save_size, 0)) || (BuildFakeTmd(&(cia->tmd), title_id, content_count, save_size, 0)) ||
(FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) ||
(WriteCiaStub(cia, path_cia) != 0)) { (!install && (WriteCiaStub(cia, path_dest) != 0))) {
free(cia); free(cia);
return 1; return 1;
} }
// insert NCSD content // insert / install NCSD content
TmdContentChunk* chunk = cia->content_list; TmdContentChunk* chunk = cia->content_list;
for (u32 i = 0; i < 3; i++) { for (u32 i = 0, idx = 0; i < 3; i++) {
NcchPartition* partition = ncsd.partitions + i; NcchPartition* partition = ncsd.partitions + i;
u32 offset = partition->offset * NCSD_MEDIA_UNIT; u32 offset = partition->offset * NCSD_MEDIA_UNIT;
u32 size = partition->size * NCSD_MEDIA_UNIT; u32 size = partition->size * NCSD_MEDIA_UNIT;
if (!size) continue; if (!size) continue;
memset(chunk, 0, sizeof(TmdContentChunk)); memset(chunk, 0, sizeof(TmdContentChunk));
chunk->id[3] = chunk->index[1] = i; chunk->id[3] = i;
if (InsertCiaContent(path_cia, path_ncsd, offset, size, chunk++, NULL, false, (i == 0), false) != 0) { chunk->index[1] = idx++;
if ((!install && (InsertCiaContent(path_dest, path_ncsd,
offset, size, chunk++, NULL, false, (i == 0), false) != 0)) ||
(install && (InstallCiaContent(path_dest, path_ncsd,
offset, size, chunk, title_id, NULL, (i == 0)) != 0))) {
free(cia); free(cia);
return 1; return 1;
} }
} }
// optional stuff (proper titlekey / meta data) // optional stuff (proper titlekey / meta data)
if (!install) {
CiaMeta* meta = (CiaMeta*) malloc(sizeof(CiaMeta)); CiaMeta* meta = (CiaMeta*) malloc(sizeof(CiaMeta));
if (meta && (BuildCiaMeta(meta, &exthdr, NULL) == 0) && if (meta && (BuildCiaMeta(meta, &exthdr, NULL) == 0) &&
(LoadExeFsFile(meta->smdh, path_ncsd, NCSD_CNT0_OFFSET, "icon", sizeof(meta->smdh), NULL) == 0) && (LoadExeFsFile(meta->smdh, path_ncsd, NCSD_CNT0_OFFSET, "icon", sizeof(meta->smdh), NULL) == 0) &&
(InsertCiaMeta(path_cia, meta) == 0)) (InsertCiaMeta(path_dest, meta) == 0))
cia->header.size_meta = CIA_META_SIZE; cia->header.size_meta = CIA_META_SIZE;
if (meta) free(meta); if (meta) free(meta);
}
// update title version from cart header (yeah, that's a bit hacky) // update title version from cart header (yeah, that's a bit hacky)
u16 title_version; u16 title_version;
@ -1644,7 +1984,8 @@ u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) {
FindTitleKey((Ticket*)&(cia->ticket), title_id); FindTitleKey((Ticket*)&(cia->ticket), title_id);
if ((FixTmdHashes(&(cia->tmd)) != 0) || if ((FixTmdHashes(&(cia->tmd)) != 0) ||
(FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) ||
(WriteCiaStub(cia, path_cia) != 0)) { (!install && (WriteCiaStub(cia, path_dest) != 0)) ||
(install && (InstallCiaSystemData(cia, path_dest) != 0))) {
free(cia); free(cia);
return 1; return 1;
} }
@ -1653,7 +1994,7 @@ u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) {
return 0; return 0;
} }
u32 BuildCiaFromNdsFile(const char* path_nds, const char* path_cia) { u32 BuildInstallFromNdsFile(const char* path_nds, const char* path_dest, bool install) {
TwlHeader twl; TwlHeader twl;
u8 title_id[8]; u8 title_id[8];
u32 save_size = 0; u32 save_size = 0;
@ -1690,15 +2031,16 @@ u32 BuildCiaFromNdsFile(const char* path_nds, const char* path_cia) {
(BuildFakeTicket((Ticket*)&(cia->ticket), title_id) != 0) || (BuildFakeTicket((Ticket*)&(cia->ticket), title_id) != 0) ||
(BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, privsave_size)) || (BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, privsave_size)) ||
(FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) ||
(WriteCiaStub(cia, path_cia) != 0)) { (!install && (WriteCiaStub(cia, path_dest) != 0))) {
free(cia); free(cia);
return 1; return 1;
} }
// insert NDS content // insert / install NDS content
TmdContentChunk* chunk = cia->content_list; TmdContentChunk* chunk = cia->content_list;
memset(chunk, 0, sizeof(TmdContentChunk)); // nothing else to do memset(chunk, 0, sizeof(TmdContentChunk)); // nothing else to do
if (InsertCiaContent(path_cia, path_nds, 0, 0, chunk, NULL, false, false, false) != 0) { if ((!install && (InsertCiaContent(path_dest, path_nds, 0, 0, chunk, NULL, false, false, false) != 0)) ||
(install && (InstallCiaContent(path_dest, path_nds, 0, 0, chunk, title_id, NULL, false) != 0))) {
free(cia); free(cia);
return 1; return 1;
} }
@ -1707,7 +2049,8 @@ u32 BuildCiaFromNdsFile(const char* path_nds, const char* path_cia) {
FindTitleKey((Ticket*)(&cia->ticket), title_id); FindTitleKey((Ticket*)(&cia->ticket), title_id);
if ((FixTmdHashes(&(cia->tmd)) != 0) || if ((FixTmdHashes(&(cia->tmd)) != 0) ||
(FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) ||
(WriteCiaStub(cia, path_cia) != 0)) { (!install && (WriteCiaStub(cia, path_dest) != 0)) ||
(install && (InstallCiaSystemData(cia, path_dest) != 0))) {
free(cia); free(cia);
return 1; return 1;
} }
@ -1747,11 +2090,11 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) {
if (filetype & GAME_TMD) if (filetype & GAME_TMD)
ret = BuildCiaFromTmdFile(path, dest, force_legit, filetype & FLAG_NUSCDN); ret = BuildCiaFromTmdFile(path, dest, force_legit, filetype & FLAG_NUSCDN);
else if (filetype & GAME_NCCH) else if (filetype & GAME_NCCH)
ret = BuildCiaFromNcchFile(path, dest); ret = BuildInstallFromNcchFile(path, dest, false);
else if (filetype & GAME_NCSD) else if (filetype & GAME_NCSD)
ret = BuildCiaFromNcsdFile(path, dest); ret = BuildInstallFromNcsdFile(path, dest, false);
else if ((filetype & GAME_NDS) && (filetype & FLAG_DSIW)) else if ((filetype & GAME_NDS) && (filetype & FLAG_DSIW))
ret = BuildCiaFromNdsFile(path, dest); ret = BuildInstallFromNdsFile(path, dest, false);
else ret = 1; else ret = 1;
if (ret != 0) // try to get rid of the borked file if (ret != 0) // try to get rid of the borked file
@ -1760,6 +2103,73 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) {
return ret; return ret;
} }
u32 InstallGameFile(const char* path, bool to_emunand, bool force_nand) {
const char* drv;
u64 filetype = IdentifyFileType(path);
u32 ret = 0;
// we need to figure out the drive based on title id
u64 title_id = 0;
if (filetype & GAME_CIA) {
CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub));
if (!cia) return 1;
if (LoadCiaStub(cia, path) != 0) {
free(cia);
return 1;
}
title_id = getbe64(cia->tmd.title_id);
free(cia);
} else if (filetype & GAME_TMD) {
TitleMetaData* tmd = (TitleMetaData*) malloc(TMD_SIZE_MAX);
if (!tmd) return 1;
if (LoadTmdFile(tmd, path) != 0) {
free(tmd);
return 1;
}
title_id = getbe64(tmd->title_id);
free(tmd);
} else if (filetype & GAME_NCCH) {
NcchHeader ncch;
if (LoadNcchHeaders(&ncch, NULL, NULL, path, 0) != 0)
return 1;
title_id = ncch.partitionId;
} else if (filetype & GAME_NCSD) {
NcsdHeader ncsd;
if (LoadNcsdHeader(&ncsd, path) != 0)
return 1;
title_id = ncsd.mediaId;
}
// decide the drive
if (((title_id >> 32) & 0x8000) || (filetype & GAME_NDS))
drv = (to_emunand ? "5:" : "2:");
else if (((title_id >> 32) & 0x10) || force_nand)
drv = (to_emunand ? "4:" : "1:");
else
drv = (to_emunand ? "B:" : "A:");
// check permissions for SysNAND
if (!CheckWritePermissions(to_emunand ? "4:" : "1:")) return 1;
// install game file
if (filetype & GAME_CIA)
ret = InstallFromCiaFile(path, drv);
else if ((filetype & GAME_TMD) && (filetype & FLAG_NUSCDN))
ret = InstallFromTmdFile(path, drv);
else if (filetype & GAME_NCCH)
ret = BuildInstallFromNcchFile(path, drv, true);
else if (filetype & GAME_NCSD)
ret = BuildInstallFromNcsdFile(path, drv, true);
else if ((filetype & GAME_NDS) && (filetype & FLAG_DSIW))
ret = BuildInstallFromNdsFile(path, drv, true);
else ret = 1;
// we have no clue what to do on failure
// if (ret != 0) ...
return ret;
}
// this has very limited uses right now // this has very limited uses right now
u32 DumpCxiSrlFromTmdFile(const char* path) { u32 DumpCxiSrlFromTmdFile(const char* path) {
u64 filetype = 0; u64 filetype = 0;

View File

@ -6,6 +6,7 @@ u32 VerifyGameFile(const char* path);
u32 CheckEncryptedGameFile(const char* path); u32 CheckEncryptedGameFile(const char* path);
u32 CryptGameFile(const char* path, bool inplace, bool encrypt); u32 CryptGameFile(const char* path, bool inplace, bool encrypt);
u32 BuildCiaFromGameFile(const char* path, bool force_legit); u32 BuildCiaFromGameFile(const char* path, bool force_legit);
u32 InstallGameFile(const char* path, bool to_emunand, bool force_nand);
u32 DumpCxiSrlFromTmdFile(const char* path); u32 DumpCxiSrlFromTmdFile(const char* path);
u32 ExtractCodeFromCxiFile(const char* path, const char* path_out, char* extstr); u32 ExtractCodeFromCxiFile(const char* path, const char* path_out, char* extstr);
u32 CompressCode(const char* path, const char* path_out); u32 CompressCode(const char* path, const char* path_out);

View File

@ -380,6 +380,14 @@ u32 CheckFixCmdCmac(const char* path, bool fix) {
} }
// we abuse the unknown u32 to mark custom, unfinished CMDs
bool fix_missing = false;
if (cmd->unknown == 0xFFFFFFFE) {
fixed = true;
cmd->unknown = 0x1;
fix_missing = true;
}
// now, check the CMAC@0x10 // now, check the CMAC@0x10
use_aeskey(keyslot); use_aeskey(keyslot);
aes_cmac(cmd_data, cmac, 1); aes_cmac(cmd_data, cmac, 1);
@ -394,7 +402,7 @@ u32 CheckFixCmdCmac(const char* path, bool fix) {
} }
// further checking will be more complicated // further checking will be more complicated
// set up pointers to cmd data (pointer arithemtic is hard) // set up pointers to cmd data (pointer arithmetic is hard)
u32 n_entries = cmd->n_entries; u32 n_entries = cmd->n_entries;
u32* cnt_id = (u32*) (cmd + 1); u32* cnt_id = (u32*) (cmd + 1);
u8* cnt_cmac = (u8*) (cnt_id + cmd->n_entries + cmd->n_cmacs); u8* cnt_cmac = (u8*) (cnt_id + cmd->n_entries + cmd->n_cmacs);
@ -406,9 +414,14 @@ u32 CheckFixCmdCmac(const char* path, bool fix) {
if (*cnt_id == 0xFFFFFFFF) continue; // unavailable content if (*cnt_id == 0xFFFFFFFF) continue; // unavailable content
snprintf(name_content, 32, "%s%08lX.app", (is_dlc) ? "00000000/" : "", *cnt_id); snprintf(name_content, 32, "%s%08lX.app", (is_dlc) ? "00000000/" : "", *cnt_id);
if (fvx_qread(path_content, hashdata, 0x100, 0x100, NULL) != FR_OK) { if (fvx_qread(path_content, hashdata, 0x100, 0x100, NULL) != FR_OK) {
if (fix_missing) {
*cnt_id = 0xFFFFFFFF;
continue;
} else {
free(cmd_data); free(cmd_data);
return 1; // failed to read content return 1; // failed to read content
} }
}
memcpy(hashdata + 0x100, &cnt_idx, 4); memcpy(hashdata + 0x100, &cnt_idx, 4);
memcpy(hashdata + 0x104, cnt_id, 4); memcpy(hashdata + 0x104, cnt_id, 4);
// hash block complete, check it // hash block complete, check it