From 79dec02e92f249011aac143552f7ae41338c253b Mon Sep 17 00:00:00 2001 From: d0k3 Date: Fri, 25 Nov 2016 18:30:01 +0100 Subject: [PATCH] Enabled (preliminary) CIA mounting support --- Makefile | 4 +- source/common.h | 5 +- source/filetype.c | 7 ++ source/filetype.h | 4 + source/fs.c | 13 ++- source/fs.h | 12 ++- source/game/cia.c | 194 +++++++++++++++++++++++++++++++++++++++ source/game/cia.h | 158 +++++++++++++++++++++++++++++++ source/game/game.h | 4 + source/godmode.c | 21 ++++- source/virtual/vgame.c | 178 +++++++++++++++++++++++++++++++++++ source/virtual/vgame.h | 12 +++ source/virtual/virtual.c | 14 ++- source/virtual/virtual.h | 9 +- source/virtual/vmem.c | 1 - source/virtual/vnand.c | 1 - 16 files changed, 616 insertions(+), 21 deletions(-) create mode 100644 source/game/cia.c create mode 100644 source/game/cia.h create mode 100644 source/game/game.h create mode 100644 source/virtual/vgame.c create mode 100644 source/virtual/vgame.h diff --git a/Makefile b/Makefile index da9e7c4..78d6a57 100644 --- a/Makefile +++ b/Makefile @@ -21,9 +21,9 @@ ifeq ($(MODE),safe) export TARGET := SafeMode9 endif BUILD := build -SOURCES := source source/fatfs source/nand source/virtual source/abstraction +SOURCES := source source/fatfs source/nand source/virtual source/game source/abstraction DATA := data -INCLUDES := source source/font source/fatfs source/nand source/virtual +INCLUDES := source source/font source/fatfs source/nand source/virtual source/game #--------------------------------------------------------------------------------- # options for code generation diff --git a/source/common.h b/source/common.h index 0ea88cd..b99053c 100644 --- a/source/common.h +++ b/source/common.h @@ -38,7 +38,7 @@ (((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v)) // GodMode9 version -#define VERSION "0.7.5" +#define VERSION "0.7.6" // buffer area defines (in use by godmode.c) #define DIR_BUFFER (0x21000000) @@ -57,6 +57,9 @@ // buffer area defines (in use by cia.c) #define GAME_BUFFER ((u8*)0x21500000) #define GAME_BUFFER_SIZE (0x100000) +// buffer area defines (in use by vgame.c) +#define VGAME_BUFFER ((u8*)0x21600000) +#define VGAME_BUFFER_SIZE (0x100000) // buffer area defines (in use by image.c, for RAMdrive) #define RAMDRV_BUFFER_O3DS ((u8*)0x22200000) // in O3DS FCRAM #define RAMDRV_SIZE_O3DS (0x01C00000) // 28MB diff --git a/source/filetype.c b/source/filetype.c index 972f5e7..2a22ff0 100644 --- a/source/filetype.c +++ b/source/filetype.c @@ -1,4 +1,5 @@ #include "filetype.h" +#include "cia.h" #include "ff.h" u32 IdentifyFileType(const char* path) { @@ -29,6 +30,12 @@ u32 IdentifyFileType(const char* path) { (header[0x1BE + 0x4] == 0xB) || (header[0x1BE + 0x4] == 0xC) || (header[0x1BE + 0x4] == 0xE))) { return IMG_FAT; // this might be an MBR -> give it the benefit of doubt } + } else if (ValidateCiaHeader((CiaHeader*) header) == 0) { + // this only works because these functions ignore CIA content index + CiaInfo info; + GetCiaInfo(&info, (CiaHeader*) header); + if (fsize >= info.size_cia) + return GAME_CIA; // CIA file } // more to come return 0; diff --git a/source/filetype.h b/source/filetype.h index 2e2d053..af11898 100644 --- a/source/filetype.h +++ b/source/filetype.h @@ -5,4 +5,8 @@ #define IMG_FAT 1 #define IMG_NAND 2 +#define GAME_CIA 3 +#define GAME_NCSD 4 +#define GAME_NCCH 5 + u32 IdentifyFileType(const char* path); diff --git a/source/fs.c b/source/fs.c index 06293c6..6776354 100644 --- a/source/fs.c +++ b/source/fs.c @@ -1,6 +1,7 @@ #include "ui.h" #include "fs.h" #include "virtual.h" +#include "vgame.h" #include "sddata.h" #include "image.h" #include "sha.h" @@ -8,7 +9,7 @@ #include "ff.h" #define NORM_FS 10 -#define VIRT_FS 7 +#define VIRT_FS 8 #define SKIP_CUR (1<<3) #define OVERWRITE_CUR (1<<4) @@ -71,6 +72,7 @@ void DeinitExtFS() { void DeinitSDCardFS() { if (GetMountState() != IMG_RAMDRV) MountImage(NULL); + MountVGameFile(NULL); if (fs_mounted[0]) { f_mount(NULL, "0:", 1); fs_mounted[0] = false; @@ -135,6 +137,8 @@ int DriveType(const char* path) { type = DRV_VIRTUAL | DRV_IMAGE; } else if (vsrc == VRT_MEMORY) { type = DRV_VIRTUAL | DRV_MEMORY; + } else if (vsrc == VRT_GAME) { + type = DRV_VIRTUAL | DRV_GAME; } } else if (CheckAliasDrive(path)) { type = DRV_FAT | DRV_ALIAS; @@ -225,6 +229,9 @@ bool CheckWritePermissions(const char* path) { } else if (drvtype & DRV_MEMORY) { perm = PERM_MEMORY; snprintf(area_name, 16, "memory areas"); + } else if (drvtype & DRV_GAME) { + perm = PERM_GAME; + snprintf(area_name, 16, "game images"); } else if ((drvtype & DRV_ALIAS) || (strncmp(path, "0:/Nintendo 3DS", 15) == 0)) { perm = PERM_SDDATA; snprintf(area_name, 16, "SD system data"); @@ -297,6 +304,7 @@ bool SetWritePermissions(u32 perm, bool add_perm) { return false; break; default: + ShowPrompt(false, "Unlock write permission is not allowed."); return false; break; #else @@ -1035,12 +1043,13 @@ bool GetRootDirContentsWorker(DirStruct* contents) { "EMUNAND CTRNAND", "EMUNAND TWLN", "EMUNAND TWLP", "IMGNAND CTRNAND", "IMGNAND TWLN", "IMGNAND TWLP", "SYSNAND SD", "EMUNAND SD", + "GAME IMAGE", "SYSNAND VIRTUAL", "EMUNAND VIRTUAL", "IMGNAND VIRTUAL", "MEMORY VIRTUAL", "LAST SEARCH" }; static const char* drvnum[] = { - "0:", "1:", "2:", "3:", "4:", "5:", "6:", "7:", "8:", "9:", "A:", "B:", "S:", "E:", "I:", "M:", "Z:" + "0:", "1:", "2:", "3:", "4:", "5:", "6:", "7:", "8:", "9:", "A:", "B:", "G:", "S:", "E:", "I:", "M:", "Z:" }; u32 n_entries = 0; diff --git a/source/fs.h b/source/fs.h index 9814225..8a5969b 100644 --- a/source/fs.h +++ b/source/fs.h @@ -14,9 +14,10 @@ #define DRV_IMAGE (1<<6) #define DRV_RAMDRIVE (1<<7) #define DRV_MEMORY (1<<8) -#define DRV_ALIAS (1<<9) -#define DRV_SEARCH (1<<10) -#define DRV_STDFAT (1<<11) // standard FAT drive without limitations +#define DRV_GAME (1<<9) +#define DRV_ALIAS (1<<10) +#define DRV_SEARCH (1<<11) +#define DRV_STDFAT (1<<12) // standard FAT drive without limitations // permission types #define PERM_SDCARD (1<<0) @@ -25,8 +26,9 @@ #define PERM_SYSNAND (1<<3) #define PERM_IMAGE (1<<4) #define PERM_MEMORY (1<<5) -#define PERM_A9LH ((1<<6) | PERM_SYSNAND) -#define PERM_SDDATA ((1<<7) | PERM_SDCARD) +#define PERM_GAME (1<<6) // can't be enabled, placeholder +#define PERM_A9LH ((1<<7) | PERM_SYSNAND) +#define PERM_SDDATA ((1<<8) | PERM_SDCARD) #define PERM_BASE (PERM_SDCARD | PERM_RAMDRIVE) #define PERM_ALL (PERM_SDCARD | PERM_RAMDRIVE | PERM_EMUNAND | PERM_SYSNAND | PERM_IMAGE | PERM_MEMORY | PERM_SDDATA) diff --git a/source/game/cia.c b/source/game/cia.c new file mode 100644 index 0000000..97098d6 --- /dev/null +++ b/source/game/cia.c @@ -0,0 +1,194 @@ +#include "cia.h" +#include "ff.h" +#include "aes.h" +#include "sha.h" + +u32 ValidateCiaHeader(CiaHeader* header) { + if ((header->size_header != CIA_HEADER_SIZE) || + (header->size_cert != CIA_CERT_SIZE) || + (header->size_ticket != CIA_TICKET_SIZE) || + (header->size_tmd < CIA_TMD_SIZE_MIN) || + (header->size_tmd > CIA_TMD_SIZE_MAX) || + (header->size_content == 0) || + ((header->size_meta != 0) && (header->size_meta != CIA_META_SIZE))) + return 1; + return 0; +} + +u32 GetCiaInfo(CiaInfo* info, CiaHeader* header) { + memcpy(info, header, 0x20); // take over first 0x20 byte + + info->offset_cert = align(header->size_header, 64); + info->offset_ticket = info->offset_cert + align(header->size_cert, 64); + info->offset_tmd = info->offset_ticket + align(header->size_ticket, 64); + info->offset_content = info->offset_tmd + align(header->size_tmd, 64); + info->offset_meta = (header->size_meta) ? info->offset_content + align(header->size_content, 64) : 0; + info->offset_ticktmd = info->offset_ticket; + info->offset_content_list = info->offset_tmd + sizeof(TitleMetaData); + + info->size_ticktmd = info->offset_content - info->offset_ticket; + info->size_content_list = info->size_tmd - sizeof(TitleMetaData); + info->size_cia = (header->size_meta) ? info->offset_meta + info->size_meta : + info->offset_content + info->size_content; + + return 0; +} + +u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd) { + TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1); + u32 content_count = getbe16(tmd->content_count); + u64 next_offset = 0; + for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) { + contents[i].offset = next_offset; + contents[i].size = getbe64(content_list[i].size); + contents[i].id = getbe32(content_list[i].id); + contents[i].index = getbe16(content_list[i].index); + contents[i].encrypted = getbe16(content_list[i].type) & 0x1; + next_offset += contents[i].size; + } + + return 0; +} + +u32 GetTitleKey(u8* titlekey, Ticket* ticket) { + // From https://github.com/profi200/Project_CTR/blob/master/makerom/pki/prod.h#L19 + static const u8 common_keyy[6][16] = { + {0xD0, 0x7B, 0x33, 0x7F, 0x9C, 0xA4, 0x38, 0x59, 0x32, 0xA2, 0xE2, 0x57, 0x23, 0x23, 0x2E, 0xB9} , // 0 - eShop Titles + {0x0C, 0x76, 0x72, 0x30, 0xF0, 0x99, 0x8F, 0x1C, 0x46, 0x82, 0x82, 0x02, 0xFA, 0xAC, 0xBE, 0x4C} , // 1 - System Titles + {0xC4, 0x75, 0xCB, 0x3A, 0xB8, 0xC7, 0x88, 0xBB, 0x57, 0x5E, 0x12, 0xA1, 0x09, 0x07, 0xB8, 0xA4} , // 2 + {0xE4, 0x86, 0xEE, 0xE3, 0xD0, 0xC0, 0x9C, 0x90, 0x2F, 0x66, 0x86, 0xD4, 0xC0, 0x6F, 0x64, 0x9F} , // 3 + {0xED, 0x31, 0xBA, 0x9C, 0x04, 0xB0, 0x67, 0x50, 0x6C, 0x44, 0x97, 0xA3, 0x5B, 0x78, 0x04, 0xFC} , // 4 + {0x5E, 0x66, 0x99, 0x8A, 0xB4, 0xE8, 0x93, 0x16, 0x06, 0x85, 0x0F, 0xD7, 0xA1, 0x6D, 0xD7, 0x55} , // 5 + }; + // From https://github.com/profi200/Project_CTR/blob/master/makerom/pki/dev.h#L21 + /* static const u8 common_key_devkit[6][16] = { // unused atm! + {0x55, 0xA3, 0xF8, 0x72, 0xBD, 0xC8, 0x0C, 0x55, 0x5A, 0x65, 0x43, 0x81, 0x13, 0x9E, 0x15, 0x3B} , // 0 - eShop Titles + {0x44, 0x34, 0xED, 0x14, 0x82, 0x0C, 0xA1, 0xEB, 0xAB, 0x82, 0xC1, 0x6E, 0x7B, 0xEF, 0x0C, 0x25} , // 1 - System Titles + {0xF6, 0x2E, 0x3F, 0x95, 0x8E, 0x28, 0xA2, 0x1F, 0x28, 0x9E, 0xEC, 0x71, 0xA8, 0x66, 0x29, 0xDC} , // 2 + {0x2B, 0x49, 0xCB, 0x6F, 0x99, 0x98, 0xD9, 0xAD, 0x94, 0xF2, 0xED, 0xE7, 0xB5, 0xDA, 0x3E, 0x27} , // 3 + {0x75, 0x05, 0x52, 0xBF, 0xAA, 0x1C, 0x04, 0x07, 0x55, 0xC8, 0xD5, 0x9A, 0x55, 0xF9, 0xAD, 0x1F} , // 4 + {0xAA, 0xDA, 0x4C, 0xA8, 0xF6, 0xE5, 0xA9, 0x77, 0xE0, 0xA0, 0xF9, 0xE4, 0x76, 0xCF, 0x0D, 0x63} , // 5 + };*/ + + // setup key 0x3D + if (ticket->commonkey_idx >= 6) return 1; + setup_aeskeyY(0x3D, (void*) common_keyy[ticket->commonkey_idx >= 6]); + use_aeskey(0x3D); + + // grab and decrypt the titlekey + u8 ctr[16] = { 0 }; + memcpy(ctr, ticket->title_id, 8); + memcpy(titlekey, ticket->titlekey, 16); + set_ctr(ctr); + aes_decrypt(titlekey, titlekey, 1, AES_CNT_TITLEKEY_DECRYPT_MODE); + + return 0; +} + +u32 BuildCiaCert(u8* ciacert) { + const u8 cert_hash_expected[0x20] = { + 0xC7, 0x2E, 0x1C, 0xA5, 0x61, 0xDC, 0x9B, 0xC8, 0x05, 0x58, 0x58, 0x9C, 0x63, 0x08, 0x1C, 0x8A, + 0x10, 0x78, 0xDF, 0x42, 0x99, 0x80, 0x3A, 0x68, 0x58, 0xF0, 0x41, 0xF9, 0xCB, 0x10, 0xE6, 0x35 + }; + + // open certs.db file on SysNAND + FIL db; + UINT bytes_read; + if (f_open(&db, "1:/dbs/certs.db", FA_READ | FA_OPEN_EXISTING) != FR_OK) + return 1; + // grab CIA cert from 4 offsets + f_lseek(&db, 0x0C10); + f_read(&db, ciacert + 0x000, 0x1F0, &bytes_read); + f_lseek(&db, 0x3A00); + f_read(&db, ciacert + 0x1F0, 0x210, &bytes_read); + f_lseek(&db, 0x3F10); + f_read(&db, ciacert + 0x400, 0x300, &bytes_read); + f_lseek(&db, 0x3C10); + f_read(&db, ciacert + 0x700, 0x300, &bytes_read); + f_close(&db); + + // check the certificate hash + u8 cert_hash[0x20]; + sha_quick(cert_hash, ciacert, CIA_CERT_SIZE, SHA256_MODE); + if (memcmp(cert_hash, cert_hash_expected, 0x20) != 0) + return 1; + + return 0; +} + +u32 BuildFakeTicket(Ticket* ticket, u8* title_id) { + const u8 sig_type[4] = { 0x00, 0x01, 0x00, 0x04 }; // RSA_2048 SHA256 + const u8 ticket_cnt_index[] = { // whatever this is + 0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x84, + 0x00, 0x00, 0x00, 0x84, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + // set ticket all zero for a clean start + memset(ticket, 0x00, sizeof(Ticket)); + // fill ticket values + memcpy(ticket->sig_type, sig_type, 4); + memset(ticket->signature, 0xFF, 0x100); + snprintf((char*) ticket->issuer, 0x40, "Root-CA00000003-XS0000000c"); + memset(ticket->ecdsa, 0xFF, 0x3C); + ticket->version = 0x01; + memset(ticket->titlekey, 0xFF, 16); + memcpy(ticket->title_id, title_id, 8); + ticket->commonkey_idx = 0x01; + ticket->audit = 0x01; // whatever + memcpy(ticket->content_index, ticket_cnt_index, sizeof(ticket_cnt_index)); + + return 0; +} + +u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) { + const u8 sig_type[4] = { 0x00, 0x01, 0x00, 0x04 }; // RSA_2048 SHA256 + // safety check: number of contents + if (n_contents > 64) return 1; // !!! + // set TMD all zero for a clean start + memset(tmd, 0x00, sizeof(TitleMetaData) + (n_contents * sizeof(TmdContentChunk))); + // file TMD values + memcpy(tmd->sig_type, sig_type, 4); + memset(tmd->signature, 0xFF, 0x100); + snprintf((char*) tmd->issuer, 0x40, "Root-CA00000003-CP0000000b"); + tmd->version = 0x01; + memcpy(tmd->title_id, title_id, 8); + tmd->title_type[3] = 0x40; // whatever + memset(tmd->save_size, 0x00, 4); // placeholder + tmd->content_count[1] = (u8) n_contents; + memset(tmd->contentinfo_hash, 0xFF, 0x20); // placeholder (hash) + tmd->contentinfo[0].cmd_count[1] = (u8) n_contents; + memset(tmd->contentinfo[0].hash, 0xFF, 0x20); // placeholder (hash) + // nothing to do for content list (yet) + + return 0; +} + +u32 LoadCiaStub(CiaStub* stub, const char* path) { + FIL file; + UINT bytes_read; + CiaInfo info; + + if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) + return 1; + + // first 0x20 byte of CIA header + f_lseek(&file, 0); + if ((f_read(&file, stub, 0x20, &bytes_read) != FR_OK) || (bytes_read != 0x20) || + (ValidateCiaHeader(&(stub->header)) != 0)) { + f_close(&file); + return 1; + } + GetCiaInfo(&info, &(stub->header)); + + // everything up till content offset + f_lseek(&file, 0); + if ((f_read(&file, stub, info.offset_content, &bytes_read) != FR_OK) || (bytes_read != info.offset_content)) { + f_close(&file); + return 1; + } + + f_close(&file); + return 0; +} diff --git a/source/game/cia.h b/source/game/cia.h new file mode 100644 index 0000000..e5bf497 --- /dev/null +++ b/source/game/cia.h @@ -0,0 +1,158 @@ +#pragma once + +#include "common.h" + +#define CIA_MAX_CONTENTS (100+1) // theme CIAs contain maximum 100 themes + 1 index content +#define CIA_HEADER_SIZE sizeof(CiaHeader) +#define CIA_CERT_SIZE 0xA00 +#define CIA_META_SIZE sizeof(CiaMeta) +#define CIA_TICKET_SIZE sizeof(Ticket) +#define CIA_TMD_SIZE_MIN sizeof(TitleMetaData) +#define CIA_TMD_SIZE_MAX (sizeof(TitleMetaData) + (CIA_MAX_CONTENTS*sizeof(TmdContentChunk))) + +// see: https://www.3dbrew.org/wiki/CIA#Meta +typedef struct { + u8 dependencies[0x180]; // from ExtHeader + u8 reserved0[0x180]; + u32 core_version; // 2 normally + u8 reserved1[0xFC]; + u8 smdh[0x36C0]; // from ExeFS +} __attribute__((packed)) CiaMeta; + +// from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tik.h#L15-L39 +typedef struct { + u8 sig_type[4]; + u8 signature[0x100]; + u8 padding1[0x3C]; + u8 issuer[0x40]; + u8 ecdsa[0x3C]; + u8 version; + u8 ca_crl_version; + u8 signer_crl_version; + u8 titlekey[0x10]; + u8 reserved0; + u8 ticket_id[8]; + u8 console_id[4]; + u8 title_id[8]; + u8 sys_access[2]; + u8 ticket_version[2]; + u8 time_mask[4]; + u8 permit_mask[4]; + u8 title_export; + u8 commonkey_idx; + u8 reserved1[0x2A]; + u8 eshop_id[4]; + u8 reserved2; + u8 audit; + u8 content_permissions[0x40]; + u8 reserved3[2]; + u8 timelimits[0x40]; + u8 content_index[0xAC]; +} __attribute__((packed)) Ticket; + +// from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tmd.h#L18-L59; +typedef struct { + u8 id[4]; + u8 index[2]; + u8 type[2]; + u8 size[8]; + u8 hash[0x20]; +} __attribute__((packed)) TmdContentChunk; + +typedef struct { + u8 index[2]; + u8 cmd_count[2]; + u8 hash[0x20]; +} __attribute__((packed)) TmdContentInfo; + +typedef struct { + u8 sig_type[4]; + u8 signature[0x100]; + u8 padding[0x3C]; + u8 issuer[0x40]; + u8 version; + u8 ca_crl_version; + u8 signer_crl_version; + u8 reserved0; + u8 system_version[8]; + u8 title_id[8]; + u8 title_type[4]; + u8 group_id[2]; + u8 save_size[4]; + u8 twl_privsave_size[4]; + u8 reserved1[4]; + u8 twl_flag; + u8 reserved2[0x31]; + u8 access_rights[4]; + u8 title_version[2]; + u8 content_count[2]; + u8 boot_content[2]; + u8 reserved3[2]; + u8 contentinfo_hash[0x20]; + TmdContentInfo contentinfo[64]; +} __attribute__((packed)) TitleMetaData; + +typedef struct { + u32 size_header; + u16 type; + u16 version; + u32 size_cert; + u32 size_ticket; + u32 size_tmd; + u32 size_meta; + u64 size_content; + u8 content_index[0x2000]; +} __attribute__((packed)) CiaHeader; + +typedef struct { + CiaHeader header; + u8 header_padding[0x40 - (CIA_HEADER_SIZE % 0x40)]; + u8 cert[CIA_CERT_SIZE]; + Ticket ticket; + u8 ticket_padding[0x40 - (CIA_TICKET_SIZE % 0x40)]; + TitleMetaData tmd; + TmdContentChunk content_list[CIA_MAX_CONTENTS]; +} __attribute__((packed)) CiaStub; + +typedef struct { // first 0x20 bytes are identical with CIA header + u32 size_header; + u16 type; + u16 version; + u32 size_cert; + u32 size_ticket; + u32 size_tmd; + u32 size_meta; + u64 size_content; + u32 size_ticktmd; + u32 size_content_list; + u64 size_cia; + u32 offset_cert; + u32 offset_ticket; + u32 offset_tmd; + u32 offset_content; + u32 offset_meta; + u32 offset_ticktmd; + u32 offset_content_list; +} __attribute__((packed)) CiaInfo; + +typedef struct { + u64 offset; + u64 size; + u32 id; + u32 index; + u8 encrypted; +} __attribute__((packed)) CiaContentInfo; + +u32 ValidateCiaHeader(CiaHeader* header); +u32 GetCiaInfo(CiaInfo* info, CiaHeader* header); +u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd); +u32 GetTitleKey(u8* titlekey, Ticket* ticket); + +u32 BuildCiaCert(u8* ciacert); +u32 BuildFakeTicket(Ticket* ticket, u8* title_id); +u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents); + +/*u32 BuildCiaMeta(Ncch ncch); +u32 InsertCiaContent(const char* path, const char* content, bool encrypt);*/ + +u32 LoadCiaStub(CiaStub* stub, const char* path); diff --git a/source/game/game.h b/source/game/game.h new file mode 100644 index 0000000..1215845 --- /dev/null +++ b/source/game/game.h @@ -0,0 +1,4 @@ +#pragma once + +#include "common.h" +#include "cia.h" diff --git a/source/godmode.c b/source/godmode.c index c49ff91..e09c91a 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -5,6 +5,7 @@ #include "platform.h" #include "nand.h" #include "virtual.h" +#include "vgame.h" #include "image.h" #define N_PANES 2 @@ -83,7 +84,7 @@ void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, DirStruct* c int drvtype = DriveType(curr_entry->path); char drvstr[32]; snprintf(drvstr, 31, "(%s%s)", - ((drvtype & DRV_SDCARD) ? "SD" : (drvtype & DRV_RAMDRIVE) ? "RAMDrive" : + ((drvtype & DRV_SDCARD) ? "SD" : (drvtype & DRV_RAMDRIVE) ? "RAMDrive" : (drvtype & DRV_RAMDRIVE) ? "Game" : (drvtype & DRV_SYSNAND) ? "SysNAND" : (drvtype & DRV_EMUNAND) ? "EmuNAND" : (drvtype & DRV_IMAGE) ? "Image" : (drvtype & DRV_MEMORY) ? "Memory" : (drvtype & DRV_ALIAS) ? "Alias" : (drvtype & DRV_SEARCH) ? "Search" : ""), ((drvtype & DRV_FAT) ? " FAT" : (drvtype & DRV_VIRTUAL) ? " Virtual" : "")); @@ -636,7 +637,9 @@ u32 GodMode() { optionstr[1] = "Calculate SHA-256"; if (injectable) optionstr[injectable-1] = "Inject data @offset"; if (mountable) optionstr[mountable-1] = - (file_type == IMG_NAND) ? "Mount as NAND image" : "Mount as FAT image"; + (file_type == IMG_NAND) ? "Mount as NAND image" : + (file_type == IMG_FAT) ? "Mount as FAT image" : + (file_type == GAME_CIA) ? "Mount as CIA image" : ""; // !!! NCCH / NCSD if (searchdrv) optionstr[searchdrv-1] = "Open containing folder"; u32 user_select = ShowSelectPrompt(n_opt, optionstr, pathstr); @@ -683,7 +686,8 @@ u32 GodMode() { ShowPrompt(false, "Failed injecting %s", origstr); clipboard->n_entries = 0; } - } else if ((int) user_select == mountable) { // -> mount as image + } else if (((int) user_select == mountable) && // -> mount as NAND / FAT image + ((file_type == IMG_NAND) || (file_type == IMG_FAT))) { if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & DRV_IMAGE)) clipboard->n_entries = 0; // remove last mounted image clipboard entries DeinitExtFS(); @@ -699,6 +703,17 @@ u32 GodMode() { GetDirContents(current_dir, current_path); cursor = 0; } + } else if ((int) user_select == mountable) { // -> mount as game image + if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & DRV_GAME)) + clipboard->n_entries = 0; // remove last mounted game clipboard entries + if (!MountVGameFile(curr_entry->path)) { + ShowPrompt(false, "Mounting game: failed"); + MountVGameFile(NULL); + } else { + *current_path = '\0'; + GetDirContents(current_dir, current_path); + cursor = 0; + } } else if ((int) user_select == searchdrv) { // -> search drive, open containing path char* last_slash = strrchr(curr_entry->path, '/'); if (last_slash) { diff --git a/source/virtual/vgame.c b/source/virtual/vgame.c new file mode 100644 index 0000000..52d9fbe --- /dev/null +++ b/source/virtual/vgame.c @@ -0,0 +1,178 @@ +#include "vgame.h" +#include "game.h" +#include "aes.h" +#include "ff.h" + +#define MAX_N_TEMPLATES 2048 // this leaves us with enough room (128kB reserved) + +#define NAME_CIA_HEADER "header.bin" +#define NAME_CIA_CERT "cert.bin" +#define NAME_CIA_TICKET "ticket.bin" +#define NAME_CIA_TMD "tmd.bin" +#define NAME_CIA_TMDCHUNK "tmdchunks.bin" +#define NAME_CIA_META "meta.bin" +#define NAME_CIA_CONTENT "%04X.%08lX.app" // index.id.app + +static FIL mount_file; +static u32 mount_state = 0; + +static VirtualFile* templates = (VirtualFile*) VGAME_BUFFER; // first 128kb reserved +static int n_templates = -1; + +static CiaStub* cia = (CiaStub*) (VGAME_BUFFER + 0xF4000); // 48kB reserved - should be enough by far +static u8 titlekey[16]; + +u32 MountVGameFile(const char* path) { + u32 type = IdentifyFileType(path); + if (mount_state) { + f_close(&mount_file); + mount_state = 0; + } + if (!path || !type) return 0; + if (type == GAME_CIA) { // for CIAs: load the CIA stub and keep it in memory + LoadCiaStub(cia, path); + GetTitleKey(titlekey, &(cia->ticket)); + } else return 0; // NCSD / NCCH handling still required + if (f_open(&mount_file, path, FA_READ | FA_WRITE | FA_OPEN_EXISTING) != FR_OK) + return false; + f_lseek(&mount_file, false); + f_sync(&mount_file); + return (mount_state = type); +} + +u32 CheckVGameDrive(void) { + return mount_state; +} + +bool BuildVGameCiaVDir(void) { + CiaInfo info; + + if ((mount_state != GAME_CIA) || (GetCiaInfo(&info, &(cia->header)) != 0)) + return false; // safety check + + // header + strncpy(templates[n_templates].name, NAME_CIA_HEADER, 32); + templates[n_templates].offset = 0; + templates[n_templates].size = info.size_header; + templates[n_templates].keyslot = 0xFF; + templates[n_templates].flags = 0; + n_templates++; + + // certificates + if (info.size_cert) { + strncpy(templates[n_templates].name, NAME_CIA_CERT, 32); + templates[n_templates].offset = info.offset_cert; + templates[n_templates].size = info.size_cert; + templates[n_templates].keyslot = 0xFF; + templates[n_templates].flags = 0; + n_templates++; + } + + // ticket + if (info.size_ticket) { + strncpy(templates[n_templates].name, NAME_CIA_TICKET, 32); + templates[n_templates].offset = info.offset_ticket; + templates[n_templates].size = info.size_ticket; + templates[n_templates].keyslot = 0xFF; + templates[n_templates].flags = 0; + n_templates++; + } + + // TMD (the full thing) + if (info.size_tmd) { + strncpy(templates[n_templates].name, NAME_CIA_TMD, 32); + templates[n_templates].offset = info.offset_tmd; + templates[n_templates].size = info.size_tmd; + templates[n_templates].keyslot = 0xFF; + templates[n_templates].flags = 0; + n_templates++; + } + + // TMD content chunks + if (info.size_content_list) { + strncpy(templates[n_templates].name, NAME_CIA_TMDCHUNK, 32); + templates[n_templates].offset = info.offset_content_list; + templates[n_templates].size = info.size_content_list; + templates[n_templates].keyslot = 0xFF; + templates[n_templates].flags = 0; + n_templates++; + } + + // meta + if (info.size_meta) { + strncpy(templates[n_templates].name, NAME_CIA_META, 32); + templates[n_templates].offset = info.offset_meta; + templates[n_templates].size = info.size_meta; + templates[n_templates].keyslot = 0xFF; + templates[n_templates].flags = 0; + n_templates++; + } + + // contents + if (info.size_content) { + TmdContentChunk* content_list = cia->content_list; + u32 content_count = getbe16(cia->tmd.content_count); + u64 next_offset = info.offset_content; + for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) { + u64 size = getbe64(content_list[i].size); + // bool encrypted = getbe16(content_list[i].type) & 0x1; + snprintf(templates[n_templates].name, 32, NAME_CIA_CONTENT, + getbe16(content_list[i].index), getbe32(content_list[i].id)); + templates[n_templates].offset = (u32) next_offset; + templates[n_templates].size = (u32) size; + templates[n_templates].keyslot = 0xFF; // even for encrypted stuff + templates[n_templates].flags = 0; // this handles encryption + n_templates++; + next_offset += size; + } + } + + return true; +} + +bool ReadVGameDir(VirtualFile* vfile, const char* path) { + (void) path; // not in use yet + static int num = -1; + + if (!vfile) { // NULL pointer + num = -1; // reset dir reader / internal number + memset(templates, 0, sizeof(VirtualFile) * MAX_N_TEMPLATES); + n_templates = 0; + if (!BuildVGameCiaVDir()) // NCCH / NCSD !!! + return false; + return true; + } + + if (++num < n_templates) { + // copy current template to vfile + memcpy(vfile, templates + num, sizeof(VirtualFile)); + return true; + } + + return false; +} + +int ReadVGameFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count) { + UINT bytes_read; + UINT ret; + u32 vfoffset = vfile->offset; + if (!count) return -1; + if (!mount_state) return FR_INVALID_OBJECT; + if (f_tell(&mount_file) != vfoffset + offset) { + if (f_size(&mount_file) < vfoffset + offset) return -1; + f_lseek(&mount_file, vfoffset + offset); + } + ret = f_read(&mount_file, buffer, count, &bytes_read); + /*if ((ret != 0) && (vfile->keyslot <= 0x40)) { // crypto + // relies on first template being the header and everything aligned to AES_BLOCK_SIZE + u32 offset_base = 0; // vfoffset - (*templates).offset; + u8 ctr[16] = { 0 }; + ctr[0] = (vfile->index & 0xFF); + ctr[1] = (vfile->index >> 8); + setup_aeskeyY(0x11, titlekey); + use_aeskey(0x11); + ctr_decrypt_boffset(buffer, buffer, bytes_read, offset - offset_base, + AES_CNT_TITLEKEY_DECRYPT_MODE, ctr); + }*/ + return (ret != 0) ? (int) ret : (bytes_read != count) ? -1 : 0; +} diff --git a/source/virtual/vgame.h b/source/virtual/vgame.h new file mode 100644 index 0000000..80b5cd3 --- /dev/null +++ b/source/virtual/vgame.h @@ -0,0 +1,12 @@ +#pragma once + +#include "common.h" +#include "filetype.h" +#include "virtual.h" + +u32 MountVGameFile(const char* path); +u32 CheckVGameDrive(void); + +bool ReadVGameDir(VirtualFile* vfile, const char* path); +int ReadVGameFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count); +// int WriteVGameFile(const VirtualFile* vfile, const u8* buffer, u32 offset, u32 count); // writing is not enabled diff --git a/source/virtual/virtual.c b/source/virtual/virtual.c index e62c167..9e84fac 100644 --- a/source/virtual/virtual.c +++ b/source/virtual/virtual.c @@ -1,13 +1,15 @@ #include "virtual.h" #include "vnand.h" #include "vmem.h" +#include "vgame.h" typedef struct { char drv_letter; u32 virtual_src; } __attribute__((packed)) VirtualDrive; -static const VirtualDrive virtualDrives[] = { {'S', VRT_SYSNAND}, {'E', VRT_EMUNAND}, {'I', VRT_IMGNAND}, {'M', VRT_MEMORY} }; +static const VirtualDrive virtualDrives[] = + { {'S', VRT_SYSNAND}, {'E', VRT_EMUNAND}, {'I', VRT_IMGNAND}, {'M', VRT_MEMORY}, {'G', VRT_GAME} }; u32 GetVirtualSource(const char* path) { // check path validity @@ -23,6 +25,8 @@ bool CheckVirtualDrive(const char* path) { u32 virtual_src = GetVirtualSource(path); if (virtual_src & (VRT_EMUNAND|VRT_IMGNAND)) return CheckVNandDrive(virtual_src); // check virtual NAND drive for EmuNAND / ImgNAND + else if (virtual_src & VRT_GAME) + return CheckVGameDrive(); return virtual_src; // this is safe for SysNAND & memory } @@ -31,6 +35,10 @@ bool ReadVirtualDir(VirtualFile* vfile, u32 virtual_src) { return ReadVNandDir(vfile, virtual_src); } else if (virtual_src & VRT_MEMORY) { return ReadVMemDir(vfile); + } else if (virtual_src & VRT_MEMORY) { + return ReadVMemDir(vfile); + } else if (virtual_src & VRT_GAME) { + return ReadVGameDir(vfile, NULL); } return false; } @@ -111,6 +119,8 @@ int ReadVirtualFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count, return ReadVNandFile(vfile, buffer, offset, count); } else if (vfile->flags & VRT_MEMORY) { return ReadVMemFile(vfile, buffer, offset, count); + } else if (vfile->flags & VRT_GAME) { + return ReadVGameFile(vfile, buffer, offset, count); } return -1; @@ -129,7 +139,7 @@ int WriteVirtualFile(const VirtualFile* vfile, const u8* buffer, u32 offset, u32 return WriteVNandFile(vfile, buffer, offset, count); } else if (vfile->flags & VRT_MEMORY) { return WriteVMemFile(vfile, buffer, offset, count); - } + } // no write support for virtual game files return -1; } diff --git a/source/virtual/virtual.h b/source/virtual/virtual.h index f7b73b5..1f241c7 100644 --- a/source/virtual/virtual.h +++ b/source/virtual/virtual.h @@ -8,17 +8,18 @@ #define VRT_EMUNAND NAND_EMUNAND #define VRT_IMGNAND NAND_IMGNAND #define VRT_MEMORY (1<<10) +#define VRT_GAME (1<<11) #define VFLAG_A9LH_AREA (1<<20) // virtual file flag (subject to change): // bits 0...9 : reserved for NAND virtual sources and info -// bits 10...19: reserved for other virtual sources -// bits 20...24: reserved for external flags +// bits 10...15: reserved for other virtual sources +// bits 16...23: reserved for external flags // bits 24...31: reserved for internal flags (different per source) typedef struct { - const char name[32]; - u32 offset; // must be a multiple of 0x200 + char name[32]; + u32 offset; // must be a multiple of 0x200 (for NAND access) u32 size; u32 keyslot; u32 flags; diff --git a/source/virtual/vmem.c b/source/virtual/vmem.c index 73b511c..fa9253c 100644 --- a/source/virtual/vmem.c +++ b/source/virtual/vmem.c @@ -40,7 +40,6 @@ bool ReadVMemDir(VirtualFile* vfile) { // found if arriving here return true; } - if (num >= n_templates) return false; return false; } diff --git a/source/virtual/vnand.c b/source/virtual/vnand.c index d9e3ece..1ed3473 100644 --- a/source/virtual/vnand.c +++ b/source/virtual/vnand.c @@ -71,7 +71,6 @@ bool ReadVNandDir(VirtualFile* vfile, u32 nand_src) { vfile->flags |= nand_src; return true; } - if (num >= n_templates) return false; return false; }