mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 05:32:47 +00:00
Enabled (preliminary) CIA mounting support
This commit is contained in:
parent
2870621dca
commit
79dec02e92
4
Makefile
4
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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
13
source/fs.c
13
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;
|
||||
|
||||
|
12
source/fs.h
12
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)
|
||||
|
||||
|
194
source/game/cia.c
Normal file
194
source/game/cia.c
Normal 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
158
source/game/cia.h
Normal 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
4
source/game/game.h
Normal file
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include "cia.h"
|
@ -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
178
source/virtual/vgame.c
Normal 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
12
source/virtual/vgame.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -40,7 +40,6 @@ bool ReadVMemDir(VirtualFile* vfile) {
|
||||
// found if arriving here
|
||||
return true;
|
||||
}
|
||||
if (num >= n_templates) return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user