diff --git a/source/common.h b/source/common.h index b99053c..3cf03b0 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.6" +#define VERSION "0.7.7" // buffer area defines (in use by godmode.c) #define DIR_BUFFER (0x21000000) diff --git a/source/filetype.c b/source/filetype.c index 2a22ff0..5caa366 100644 --- a/source/filetype.c +++ b/source/filetype.c @@ -1,5 +1,5 @@ #include "filetype.h" -#include "cia.h" +#include "game.h" #include "ff.h" u32 IdentifyFileType(const char* path) { @@ -36,7 +36,10 @@ u32 IdentifyFileType(const char* path) { GetCiaInfo(&info, (CiaHeader*) header); if (fsize >= info.size_cia) return GAME_CIA; // CIA file - } // more to come + } else if (ValidateNcsdHeader((NcsdHeader*) header) == 0) { + if (fsize >= (((NcsdHeader*) header)->size * NCSD_MEDIA_UNIT)) + return GAME_NCSD; // NCSD (".3DS") file + } // NCCH still missing return 0; } diff --git a/source/game/game.h b/source/game/game.h index 1215845..527dbcd 100644 --- a/source/game/game.h +++ b/source/game/game.h @@ -2,3 +2,4 @@ #include "common.h" #include "cia.h" +#include "ncsd.h" diff --git a/source/game/ncsd.c b/source/game/ncsd.c new file mode 100644 index 0000000..a8fd2ac --- /dev/null +++ b/source/game/ncsd.c @@ -0,0 +1,22 @@ +#include "ncsd.h" + +u32 ValidateNcsdHeader(NcsdHeader* header) { + u8 zeroes[16] = { 0 }; + if ((memcmp(header->magic, "NCSD", 4) != 0) || // check magic number + (memcmp(header->partitions_fs_type, zeroes, 8) != 0)) // prevent detection of NAND images + return 1; + + u32 data_units = 0; + for (u32 i = 0; i < 8; i++) { + NcchPartition* partition = header->partitions + i; + if ((partition->offset == 0) && (partition->size == 0)) + continue; + if (partition->offset < data_units) + return 1; // overlapping partitions, failed + data_units = partition->offset + partition->size; + } + if (data_units > header->size) + return 1; + + return 0; +} diff --git a/source/game/ncsd.h b/source/game/ncsd.h new file mode 100644 index 0000000..5457ecd --- /dev/null +++ b/source/game/ncsd.h @@ -0,0 +1,30 @@ +#pragma once + +#include "common.h" + +#define NCSD_MEDIA_UNIT 0x200 + +// see: https://www.3dbrew.org/wiki/NCSD +typedef struct { + u32 offset; + u32 size; +} __attribute__((packed)) NcchPartition; + +// see: https://www.3dbrew.org/wiki/NCSD +typedef struct { + u8 signature[0x100]; + u8 magic[4]; + u32 size; + u64 mediaId; + u8 partitions_fs_type[8]; + u8 partitions_crypto_type[8]; + NcchPartition partitions[8]; + u8 hash_exthdr[0x20]; + u8 size_addhdr[0x4]; + u8 sector_zero_offset[0x4]; + u8 partition_flags[8]; + u8 partitionId_table[8][8]; + u8 reserved[0x40]; +} __attribute__((packed, aligned(16))) NcsdHeader; + +u32 ValidateNcsdHeader(NcsdHeader* header); diff --git a/source/godmode.c b/source/godmode.c index 7fa4e12..67da83a 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -639,7 +639,8 @@ u32 GodMode() { if (mountable) optionstr[mountable-1] = (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 + (file_type == GAME_CIA) ? "Mount as CIA image" : + (file_type == GAME_NCSD) ? "Mount as NCSD image" : ""; // !!! NCCH 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 7278643..e40a4b3 100644 --- a/source/virtual/vgame.c +++ b/source/virtual/vgame.c @@ -14,10 +14,18 @@ #define NAME_CIA_META "meta.bin" #define NAME_CIA_CONTENT "%04X.%08lX.app" // index.id.app +#define NAME_NCSD_HEADER "ncsd.bin" +#define NAME_NCSD_CARDINFO "cardinfo.bin" +#define NAME_NCSD_DEVINFO "devinfo.bin" +#define NAME_NCSD_CONTENT "cnt0.game.cxi", "cnt1.manual.cfa", "cnt2.dlp.cfa", \ + "cnt3.unk", "cnt4.unk", "cnt5.unk", \ + "cnt6.update_n3ds.cfa", "cnt7.update_o3ds.cfa" + 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 u32 InitVGameDrive(void) { // prerequisite: game file mounted as image @@ -30,7 +38,11 @@ u32 InitVGameDrive(void) { // prerequisite: game file mounted as image (GetCiaInfo(&info, &(cia->header)) != 0) || (ReadImageBytes((u8*) cia, 0, info.offset_content) != 0)) return 0; - } else if ((type == GAME_NCCH) || (type == GAME_NCSD)) { + } else if (type == GAME_NCSD) { + if ((ReadImageBytes((u8*) ncsd, 0, sizeof(NcsdHeader)) != 0) || + (ValidateNcsdHeader(ncsd) != 0)) + return 0; + } else if (type == GAME_NCCH) { } else return 0; // not a mounted game file vgame_type = type; @@ -42,6 +54,56 @@ u32 CheckVGameDrive(void) { return vgame_type; } +bool BuildVGameNcsdVDir(void) { + const char* name_content[] = { NAME_NCSD_CONTENT }; + + if (CheckVGameDrive() != GAME_NCSD) + return false; // safety check + + // header + strncpy(templates[n_templates].name, NAME_NCSD_HEADER, 32); + templates[n_templates].offset = 0; + templates[n_templates].size = 0x200; + templates[n_templates].keyslot = 0xFF; + templates[n_templates].flags = 0; + n_templates++; + + // card info header + if (ncsd->partitions[0].offset * NCSD_MEDIA_UNIT >= 0x1200) { + strncpy(templates[n_templates].name, NAME_NCSD_CARDINFO, 32); + templates[n_templates].offset = 0x200; + templates[n_templates].size = 0x1000; + templates[n_templates].keyslot = 0xFF; + templates[n_templates].flags = 0; + n_templates++; + } + + // dev info header + if (ncsd->partitions[0].offset * NCSD_MEDIA_UNIT >= 0x1500) { + strncpy(templates[n_templates].name, NAME_NCSD_DEVINFO, 32); + templates[n_templates].offset = 0x1200; + templates[n_templates].size = 0x300; + templates[n_templates].keyslot = 0xFF; + templates[n_templates].flags = 0; + n_templates++; + } + + // contents + for (u32 i = 0; i < 8; i++) { + NcchPartition* partition = ncsd->partitions + i; + if ((partition->offset == 0) && (partition->size == 0)) + continue; + strncpy(templates[n_templates].name, name_content[i], 32); + templates[n_templates].offset = partition->offset * NCSD_MEDIA_UNIT; + templates[n_templates].size = partition->size * NCSD_MEDIA_UNIT; + templates[n_templates].keyslot = 0xFF; // even for encrypted stuff + templates[n_templates].flags = 0; // this handles encryption + n_templates++; + } + + return true; +} + bool BuildVGameCiaVDir(void) { CiaInfo info; @@ -129,6 +191,7 @@ bool BuildVGameCiaVDir(void) { } bool ReadVGameDir(VirtualFile* vfile, const char* path) { + (void) path; // not in use yet static int num = -1; @@ -136,9 +199,11 @@ bool ReadVGameDir(VirtualFile* vfile, const char* path) { 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 ((vgame_type == GAME_CIA) && BuildVGameCiaVDir()) // for CIA + return true; + else if ((vgame_type == GAME_NCSD) && BuildVGameNcsdVDir()) // for NCSD + return true; + return false; } if (++num < n_templates) {