diff --git a/source/filetype.c b/source/filetype.c index 5caa366..2c4ee40 100644 --- a/source/filetype.c +++ b/source/filetype.c @@ -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; } diff --git a/source/game/game.h b/source/game/game.h index 527dbcd..51aff25 100644 --- a/source/game/game.h +++ b/source/game/game.h @@ -3,3 +3,4 @@ #include "common.h" #include "cia.h" #include "ncsd.h" +#include "ncch.h" diff --git a/source/game/ncch.c b/source/game/ncch.c new file mode 100644 index 0000000..f550813 --- /dev/null +++ b/source/game/ncch.c @@ -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; +} diff --git a/source/game/ncch.h b/source/game/ncch.h new file mode 100644 index 0000000..1cbecf5 --- /dev/null +++ b/source/game/ncch.h @@ -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); diff --git a/source/game/ncsd.h b/source/game/ncsd.h index 5457ecd..14ab3ea 100644 --- a/source/game/ncsd.h +++ b/source/game/ncsd.h @@ -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); diff --git a/source/godmode.c b/source/godmode.c index 67da83a..11eefe1 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -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); diff --git a/source/virtual/vgame.c b/source/virtual/vgame.c index e40a4b3..80f7b01 100644 --- a/source/virtual/vgame.c +++ b/source/virtual/vgame.c @@ -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; } diff --git a/source/virtual/virtual.h b/source/virtual/virtual.h index 1f241c7..763bab0 100644 --- a/source/virtual/virtual.h +++ b/source/virtual/virtual.h @@ -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)