mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 05:32:47 +00:00
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:
parent
8a7448995f
commit
d2c47b7977
@ -50,6 +50,7 @@
|
||||
#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_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_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB))
|
||||
#define FTYPE_KEYBUILD(tp) (tp&(BIN_KEYDB|BIN_LEGKEY))
|
||||
|
@ -2,29 +2,9 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "ticket.h"
|
||||
|
||||
// There's probably a better place to put this
|
||||
#define SD_TITLEDB_PATH(emu) ((emu) ? "B:/dbs/title.db" : "A:/dbs/title.db")
|
||||
#include "tie.h"
|
||||
|
||||
// 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 GetNumTickets(const char* path);
|
||||
|
@ -9,3 +9,42 @@ u32 CheckCmdSize(CmdHeader* cmd, u64 fsize) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#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
|
||||
@ -16,3 +20,4 @@ typedef struct {
|
||||
} __attribute__((packed, aligned(4))) CmdHeader;
|
||||
|
||||
u32 CheckCmdSize(CmdHeader* cmd, u64 fsize);
|
||||
u32 BuildCmdData(CmdHeader* cmd, TitleMetaData* tmd);
|
||||
|
@ -14,6 +14,9 @@
|
||||
#include "tad.h"
|
||||
#include "3dsx.h"
|
||||
#include "tmd.h"
|
||||
#include "ticket.h"
|
||||
#include "tie.h"
|
||||
#include "cmd.h"
|
||||
#include "bdri.h"
|
||||
#include "ticketdb.h"
|
||||
#include "ncchinfo.h"
|
||||
|
92
arm9/source/game/tie.c
Normal file
92
arm9/source/game/tie.c
Normal 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
31
arm9/source/game/tie.h
Normal 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);
|
@ -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 cia_buildable = (FTYPE_CIABUILD(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 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 ciacheckable = (FTYPE_CIACHECK(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_legit = (cia_buildable_legit) ? ++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_dec = (tik_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_legit > 0) optionstr[cia_build_legit-1] = "Build CIA (legit)";
|
||||
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_dec > 0) optionstr[tik_build_dec-1] = "Build " TIKDB_NAME_DEC;
|
||||
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;
|
||||
}
|
||||
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
|
||||
if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) {
|
||||
u32 n_success = 0;
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "gameutil.h"
|
||||
#include "nandcmac.h"
|
||||
#include "disadiff.h"
|
||||
#include "game.h"
|
||||
#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;
|
||||
}
|
||||
|
||||
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,
|
||||
TmdContentChunk* chunk, const u8* titlekey, bool force_legit, bool cxi_fix, bool cdn_decrypt) {
|
||||
// crypto types / ctr
|
||||
@ -1330,7 +1594,53 @@ u32 InsertCiaMeta(const char* path_cia, CiaMeta* meta) {
|
||||
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 };
|
||||
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 };
|
||||
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++) {
|
||||
TmdContentChunk* chunk = &(content_list[i]);
|
||||
if (present[i / 8] & (1 << (i % 8))) {
|
||||
snprintf(name_content, 256 - (name_content - path_content),
|
||||
(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));
|
||||
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
|
||||
if (!install) {
|
||||
CiaMeta* meta = (CiaMeta*) malloc(sizeof(CiaMeta));
|
||||
if (meta) {
|
||||
if (content_count && cdn) {
|
||||
if (!force_legit || !(getbe16(content_list->type) & 0x01)) {
|
||||
CiaInfo info;
|
||||
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;
|
||||
}
|
||||
} else if (content_count) {
|
||||
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;
|
||||
}
|
||||
free(meta);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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));
|
||||
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);
|
||||
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;
|
||||
NcchHeader ncch;
|
||||
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) ||
|
||||
(BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, 0)) ||
|
||||
(FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) ||
|
||||
(WriteCiaStub(cia, path_cia) != 0)) {
|
||||
(!install && (WriteCiaStub(cia, path_dest) != 0))) {
|
||||
free(cia);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// insert NCCH content
|
||||
// insert / install NCCH content
|
||||
TmdContentChunk* chunk = cia->content_list;
|
||||
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);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// optional stuff (proper titlekey / meta data)
|
||||
if (!install) {
|
||||
CiaMeta* meta = (CiaMeta*) malloc(sizeof(CiaMeta));
|
||||
if (meta && has_exthdr && (BuildCiaMeta(meta, &exthdr, 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;
|
||||
free(meta);
|
||||
}
|
||||
|
||||
// write the CIA stub (take #2)
|
||||
FindTitleKey((Ticket*)(&cia->ticket), title_id);
|
||||
if ((FixTmdHashes(&(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);
|
||||
return 1;
|
||||
}
|
||||
@ -1570,7 +1904,7 @@ u32 BuildCiaFromNcchFile(const char* path_ncch, const char* path_cia) {
|
||||
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;
|
||||
NcsdHeader ncsd;
|
||||
NcchHeader ncch;
|
||||
@ -1602,33 +1936,39 @@ u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) {
|
||||
(BuildFakeTicket((Ticket*)&(cia->ticket), title_id) != 0) ||
|
||||
(BuildFakeTmd(&(cia->tmd), title_id, content_count, save_size, 0)) ||
|
||||
(FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) ||
|
||||
(WriteCiaStub(cia, path_cia) != 0)) {
|
||||
(!install && (WriteCiaStub(cia, path_dest) != 0))) {
|
||||
free(cia);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// insert NCSD content
|
||||
// insert / install NCSD content
|
||||
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;
|
||||
u32 offset = partition->offset * NCSD_MEDIA_UNIT;
|
||||
u32 size = partition->size * NCSD_MEDIA_UNIT;
|
||||
if (!size) continue;
|
||||
memset(chunk, 0, sizeof(TmdContentChunk));
|
||||
chunk->id[3] = chunk->index[1] = i;
|
||||
if (InsertCiaContent(path_cia, path_ncsd, offset, size, chunk++, NULL, false, (i == 0), false) != 0) {
|
||||
chunk->id[3] = i;
|
||||
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);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// optional stuff (proper titlekey / meta data)
|
||||
if (!install) {
|
||||
CiaMeta* meta = (CiaMeta*) malloc(sizeof(CiaMeta));
|
||||
if (meta && (BuildCiaMeta(meta, &exthdr, 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;
|
||||
if (meta) free(meta);
|
||||
}
|
||||
|
||||
// update title version from cart header (yeah, that's a bit hacky)
|
||||
u16 title_version;
|
||||
@ -1644,7 +1984,8 @@ u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) {
|
||||
FindTitleKey((Ticket*)&(cia->ticket), title_id);
|
||||
if ((FixTmdHashes(&(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);
|
||||
return 1;
|
||||
}
|
||||
@ -1653,7 +1994,7 @@ u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) {
|
||||
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;
|
||||
u8 title_id[8];
|
||||
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) ||
|
||||
(BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, privsave_size)) ||
|
||||
(FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) ||
|
||||
(WriteCiaStub(cia, path_cia) != 0)) {
|
||||
(!install && (WriteCiaStub(cia, path_dest) != 0))) {
|
||||
free(cia);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// insert NDS content
|
||||
// insert / install NDS content
|
||||
TmdContentChunk* chunk = cia->content_list;
|
||||
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);
|
||||
return 1;
|
||||
}
|
||||
@ -1707,7 +2049,8 @@ u32 BuildCiaFromNdsFile(const char* path_nds, const char* path_cia) {
|
||||
FindTitleKey((Ticket*)(&cia->ticket), title_id);
|
||||
if ((FixTmdHashes(&(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);
|
||||
return 1;
|
||||
}
|
||||
@ -1747,11 +2090,11 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) {
|
||||
if (filetype & GAME_TMD)
|
||||
ret = BuildCiaFromTmdFile(path, dest, force_legit, filetype & FLAG_NUSCDN);
|
||||
else if (filetype & GAME_NCCH)
|
||||
ret = BuildCiaFromNcchFile(path, dest);
|
||||
ret = BuildInstallFromNcchFile(path, dest, false);
|
||||
else if (filetype & GAME_NCSD)
|
||||
ret = BuildCiaFromNcsdFile(path, dest);
|
||||
ret = BuildInstallFromNcsdFile(path, dest, false);
|
||||
else if ((filetype & GAME_NDS) && (filetype & FLAG_DSIW))
|
||||
ret = BuildCiaFromNdsFile(path, dest);
|
||||
ret = BuildInstallFromNdsFile(path, dest, false);
|
||||
else ret = 1;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
u32 DumpCxiSrlFromTmdFile(const char* path) {
|
||||
u64 filetype = 0;
|
||||
|
@ -6,6 +6,7 @@ u32 VerifyGameFile(const char* path);
|
||||
u32 CheckEncryptedGameFile(const char* path);
|
||||
u32 CryptGameFile(const char* path, bool inplace, bool encrypt);
|
||||
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 ExtractCodeFromCxiFile(const char* path, const char* path_out, char* extstr);
|
||||
u32 CompressCode(const char* path, const char* path_out);
|
||||
|
@ -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
|
||||
use_aeskey(keyslot);
|
||||
aes_cmac(cmd_data, cmac, 1);
|
||||
@ -394,7 +402,7 @@ u32 CheckFixCmdCmac(const char* path, bool fix) {
|
||||
}
|
||||
|
||||
// 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* cnt_id = (u32*) (cmd + 1);
|
||||
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
|
||||
snprintf(name_content, 32, "%s%08lX.app", (is_dlc) ? "00000000/" : "", *cnt_id);
|
||||
if (fvx_qread(path_content, hashdata, 0x100, 0x100, NULL) != FR_OK) {
|
||||
if (fix_missing) {
|
||||
*cnt_id = 0xFFFFFFFF;
|
||||
continue;
|
||||
} else {
|
||||
free(cmd_data);
|
||||
return 1; // failed to read content
|
||||
}
|
||||
}
|
||||
memcpy(hashdata + 0x100, &cnt_idx, 4);
|
||||
memcpy(hashdata + 0x104, cnt_id, 4);
|
||||
// hash block complete, check it
|
||||
|
Loading…
x
Reference in New Issue
Block a user