Enable mounting of NCCH files

This commit is contained in:
d0k3 2016-11-28 01:19:12 +01:00
parent 622947f63a
commit 58d7573ef5
8 changed files with 199 additions and 17 deletions

View File

@ -3,7 +3,7 @@
#include "ff.h"
u32 IdentifyFileType(const char* path) {
u8 header[0x200]; // minimum required size
u8 __attribute__((aligned(16))) header[0x200]; // minimum required size
FIL file;
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 0;
@ -37,9 +37,14 @@ u32 IdentifyFileType(const char* path) {
if (fsize >= info.size_cia)
return GAME_CIA; // CIA file
} else if (ValidateNcsdHeader((NcsdHeader*) header) == 0) {
if (fsize >= (((NcsdHeader*) header)->size * NCSD_MEDIA_UNIT))
NcsdHeader* ncsd = (NcsdHeader*) header;
if (fsize >= (ncsd->size * NCSD_MEDIA_UNIT))
return GAME_NCSD; // NCSD (".3DS") file
} // NCCH still missing
} else if (ValidateNcchHeader((NcchHeader*) header) == 0) {
NcchHeader* ncch = (NcchHeader*) header;
if (fsize >= (ncch->size * NCCH_MEDIA_UNIT))
return GAME_NCCH; // NCSD (".3DS") file
}
return 0;
}

View File

@ -3,3 +3,4 @@
#include "common.h"
#include "cia.h"
#include "ncsd.h"
#include "ncch.h"

24
source/game/ncch.c Normal file
View File

@ -0,0 +1,24 @@
#include "ncch.h"
u32 ValidateNcchHeader(NcchHeader* header) {
if (memcmp(header->magic, "NCCH", 4) != 0) // check magic number
return 1;
u32 ncch_units = (NCCH_EXTHDR_OFFSET + header->size_exthdr) / NCCH_MEDIA_UNIT; // exthdr
if (header->size_plain) { // plain region
if (header->offset_plain < ncch_units) return 1; // overlapping plain region
ncch_units = (header->offset_plain + header->size_plain);
}
if (header->size_exefs) { // ExeFS
if (header->offset_exefs < ncch_units) return 1; // overlapping exefs region
ncch_units = (header->offset_exefs + header->size_exefs);
}
if (header->size_romfs) { // RomFS
if (header->offset_romfs < ncch_units) return 1; // overlapping romfs region
ncch_units = (header->offset_romfs + header->size_romfs);
}
// size check
if (ncch_units > header->size) return 1;
return 0;
}

59
source/game/ncch.h Normal file
View File

@ -0,0 +1,59 @@
#pragma once
#include "common.h"
#define NCCH_MEDIA_UNIT 0x200
#define NCCH_EXTHDR_SIZE 0x800 // NCCH header says 0x400, which is not the full thing
#define NCCH_EXTHDR_OFFSET 0x200
// see: https://www.3dbrew.org/wiki/NCCH/Extended_Header
// very limited, contains only required stuff
typedef struct {
char name[8];
u8 reserved[0x5];
u8 flag; // bit 1 for SD
u32 remaster_version;
u8 sci_data[0x30];
u8 dependencies[0x180];
u8 sys_info[0x40];
u8 aci_data[0x200];
u8 signature[0x100];
u8 public_key[0x100];
u8 aci_limit_data[0x200];
} __attribute__((packed)) NcchExtHeader;
// see: https://www.3dbrew.org/wiki/NCCH#NCCH_Header
typedef struct {
u8 signature[0x100];
u8 magic[0x4];
u32 size;
u64 partitionId;
u16 makercode;
u16 version;
u8 hash_seed[0x4];
u64 programId;
u8 reserved0[0x10];
u8 hash_logo[0x20];
char productcode[0x10];
u8 hash_exthdr[0x20];
u32 size_exthdr;
u8 reserved1[0x4];
u8 flags[0x8];
u32 offset_plain;
u32 size_plain;
u32 offset_logo;
u32 size_logo;
u32 offset_exefs;
u32 size_exefs;
u32 size_exefs_hash;
u8 reserved2[0x4];
u32 offset_romfs;
u32 size_romfs;
u32 size_romfs_hash;
u8 reserved3[0x4];
u8 hash_exefs[0x20];
u8 hash_romfs[0x20];
} __attribute__((packed)) NcchHeader;
u32 ValidateNcchHeader(NcchHeader* header);

View File

@ -2,15 +2,19 @@
#include "common.h"
#define NCSD_MEDIA_UNIT 0x200
#define NCSD_MEDIA_UNIT 0x200
#define NCSD_CINFO_OFFSET 0x200
#define NCSD_CINFO_SIZE 0x1000
#define NCSD_DINFO_OFFSET 0x1200
#define NCSD_DINFO_SIZE 0x300
// see: https://www.3dbrew.org/wiki/NCSD
typedef struct {
u32 offset;
u32 size;
} __attribute__((packed)) NcchPartition;
// see: https://www.3dbrew.org/wiki/NCSD
// see: https://www.3dbrew.org/wiki/NCSD#NCSD_header
typedef struct {
u8 signature[0x100];
u8 magic[4];
@ -25,6 +29,6 @@ typedef struct {
u8 partition_flags[8];
u8 partitionId_table[8][8];
u8 reserved[0x40];
} __attribute__((packed, aligned(16))) NcsdHeader;
} __attribute__((packed)) NcsdHeader;
u32 ValidateNcsdHeader(NcsdHeader* header);

View File

@ -640,7 +640,8 @@ u32 GodMode() {
(file_type == IMG_NAND) ? "Mount as NAND image" :
(file_type == IMG_FAT) ? "Mount as FAT image" :
(file_type == GAME_CIA) ? "Mount as CIA image" :
(file_type == GAME_NCSD) ? "Mount as NCSD image" : ""; // !!! NCCH
(file_type == GAME_NCSD) ? "Mount as NCSD image" :
(file_type == GAME_NCCH) ? "Mount as NCCH image" : "???";
if (searchdrv) optionstr[searchdrv-1] = "Open containing folder";
u32 user_select = ShowSelectPrompt(n_opt, optionstr, pathstr);

View File

@ -4,6 +4,10 @@
#include "aes.h"
#include "ff.h"
#define VFLAG_EXTHDR (1<<29)
#define VFLAG_EXEFS (1<<30)
#define VFLAG_ROMFS (1<<31)
#define MAX_N_TEMPLATES 2048 // this leaves us with enough room (128kB reserved)
#define NAME_CIA_HEADER "header.bin"
@ -21,12 +25,25 @@
"cnt3.unk", "cnt4.unk", "cnt5.unk", \
"cnt6.update_n3ds.cfa", "cnt7.update_o3ds.cfa"
#define NAME_NCCH_HEADER "ncch.bin"
#define NAME_NCCH_EXTHEADER "extheader.bin"
#define NAME_NCCH_PLAIN "plain.bin"
#define NAME_NCCH_LOGO "logo.bin"
#define NAME_NCCH_EXEFS "exefs.bin"
#define NAME_NCCH_ROMFS "romfs.bin"
static u32 vgame_type = 0;
static VirtualFile* templates = (VirtualFile*) VGAME_BUFFER; // first 128kb reserved
static int n_templates = -1;
static NcsdHeader* ncsd = (NcsdHeader*) (VGAME_BUFFER + 0xF3000); // needs only 512 byte
static CiaStub* cia = (CiaStub*) (VGAME_BUFFER + 0xF4000); // 48kB reserved - should be enough by far
static NcsdHeader* ncsd = (NcsdHeader*) (VGAME_BUFFER + 0xF3000); // needs only 512 byte
static NcchHeader* ncch = (NcchHeader*) (VGAME_BUFFER + 0xF3200); // needs only 512 byte
static ExeFsHeader* exefs = (ExeFsHeader*) (VGAME_BUFFER + 0xF3400); // needs only 512 byte
static u32 offset_ncch = 0;
static u32 offset_exefs = 0;
static u32 offset_romfs = 0;
u32 InitVGameDrive(void) { // prerequisite: game file mounted as image
u32 type = GetMountState();
@ -43,6 +60,10 @@ u32 InitVGameDrive(void) { // prerequisite: game file mounted as image
(ValidateNcsdHeader(ncsd) != 0))
return 0;
} else if (type == GAME_NCCH) {
offset_ncch = 0;
if ((ReadImageBytes((u8*) ncch, 0, sizeof(NcchHeader)) != 0) ||
(ValidateNcchHeader(ncch) != 0))
return 0;
} else return 0; // not a mounted game file
vgame_type = type;
@ -54,6 +75,71 @@ u32 CheckVGameDrive(void) {
return vgame_type;
}
bool BuildVGameNcchVDir(void) {
if (CheckVGameDrive() != GAME_NCCH)
return false; // safety check
// header
strncpy(templates[n_templates].name, NAME_NCCH_HEADER, 32);
templates[n_templates].offset = offset_ncch + 0;
templates[n_templates].size = 0x200;
templates[n_templates].keyslot = 0xFF;
templates[n_templates].flags = 0;
n_templates++;
// extended header
if (ncch->size_exthdr) {
strncpy(templates[n_templates].name, NAME_NCCH_EXTHEADER, 32);
templates[n_templates].offset = offset_ncch + NCCH_EXTHDR_OFFSET;
templates[n_templates].size = NCCH_EXTHDR_SIZE;
templates[n_templates].keyslot = 0xFF; // crypto ?
templates[n_templates].flags = 0;
n_templates++;
}
// plain region
if (ncch->size_plain) {
strncpy(templates[n_templates].name, NAME_NCCH_PLAIN, 32);
templates[n_templates].offset = offset_ncch + (ncch->offset_plain * NCCH_MEDIA_UNIT);
templates[n_templates].size = ncch->size_plain * NCCH_MEDIA_UNIT;
templates[n_templates].keyslot = 0xFF;
templates[n_templates].flags = 0;
n_templates++;
}
// logo region
if (ncch->size_logo) {
strncpy(templates[n_templates].name, NAME_NCCH_LOGO, 32);
templates[n_templates].offset =offset_ncch + (ncch->offset_logo * NCCH_MEDIA_UNIT);
templates[n_templates].size = ncch->size_logo * NCCH_MEDIA_UNIT;
templates[n_templates].keyslot = 0xFF;
templates[n_templates].flags = 0;
n_templates++;
}
// exefs
if (ncch->size_exefs) {
strncpy(templates[n_templates].name, NAME_NCCH_EXEFS, 32);
templates[n_templates].offset = offset_ncch + (ncch->offset_exefs * NCCH_MEDIA_UNIT);
templates[n_templates].size = ncch->size_exefs * NCCH_MEDIA_UNIT;
templates[n_templates].keyslot = 0xFF;
templates[n_templates].flags = 0;
n_templates++;
}
// romfs
if (ncch->size_romfs) {
strncpy(templates[n_templates].name, NAME_NCCH_ROMFS, 32);
templates[n_templates].offset = offset_ncch + (ncch->offset_romfs * NCCH_MEDIA_UNIT);
templates[n_templates].size = ncch->size_romfs * NCCH_MEDIA_UNIT;
templates[n_templates].keyslot = 0xFF;
templates[n_templates].flags = 0;
n_templates++;
}
return 0;
}
bool BuildVGameNcsdVDir(void) {
const char* name_content[] = { NAME_NCSD_CONTENT };
@ -69,20 +155,20 @@ bool BuildVGameNcsdVDir(void) {
n_templates++;
// card info header
if (ncsd->partitions[0].offset * NCSD_MEDIA_UNIT >= 0x1200) {
if (ncsd->partitions[0].offset * NCSD_MEDIA_UNIT >= NCSD_CINFO_OFFSET + NCSD_CINFO_SIZE) {
strncpy(templates[n_templates].name, NAME_NCSD_CARDINFO, 32);
templates[n_templates].offset = 0x200;
templates[n_templates].size = 0x1000;
templates[n_templates].offset = NCSD_CINFO_OFFSET;
templates[n_templates].size = NCSD_CINFO_SIZE;
templates[n_templates].keyslot = 0xFF;
templates[n_templates].flags = 0;
n_templates++;
}
// dev info header
if (ncsd->partitions[0].offset * NCSD_MEDIA_UNIT >= 0x1500) {
if (ncsd->partitions[0].offset * NCSD_MEDIA_UNIT >= NCSD_DINFO_OFFSET + NCSD_DINFO_SIZE) {
strncpy(templates[n_templates].name, NAME_NCSD_DEVINFO, 32);
templates[n_templates].offset = 0x1200;
templates[n_templates].size = 0x300;
templates[n_templates].offset = NCSD_DINFO_OFFSET;
templates[n_templates].size = NCSD_DINFO_SIZE;
templates[n_templates].keyslot = 0xFF;
templates[n_templates].flags = 0;
n_templates++;
@ -203,6 +289,8 @@ bool ReadVGameDir(VirtualFile* vfile, const char* path) {
return true;
else if ((vgame_type == GAME_NCSD) && BuildVGameNcsdVDir()) // for NCSD
return true;
else if ((vgame_type == GAME_NCCH) && BuildVGameNcchVDir()) // for NCSD
return true;
return false;
}

View File

@ -15,8 +15,8 @@
// virtual file flag (subject to change):
// bits 0...9 : reserved for NAND virtual sources and info
// 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)
// bits 16...19: reserved for external flags
// bits 20...31: reserved for internal flags (different per source)
typedef struct {
char name[32];
u32 offset; // must be a multiple of 0x200 (for NAND access)