Enabled (preliminary) CIA mounting support

This commit is contained in:
d0k3 2016-11-25 18:30:01 +01:00
parent 2870621dca
commit 79dec02e92
16 changed files with 616 additions and 21 deletions

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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)

194
source/game/cia.c Normal file
View File

@ -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;
}

158
source/game/cia.h Normal file
View File

@ -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);

4
source/game/game.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#include "common.h"
#include "cia.h"

View File

@ -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) {

178
source/virtual/vgame.c Normal file
View File

@ -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;
}

12
source/virtual/vgame.h Normal file
View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -40,7 +40,6 @@ bool ReadVMemDir(VirtualFile* vfile) {
// found if arriving here
return true;
}
if (num >= n_templates) return false;
return false;
}

View File

@ -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;
}