Enable mounting of NCSD files

This commit is contained in:
d0k3 2016-11-26 15:37:53 +01:00
parent 2c5a46522d
commit 622947f63a
7 changed files with 130 additions and 8 deletions

View File

@ -38,7 +38,7 @@
(((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v)) (((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v))
// GodMode9 version // GodMode9 version
#define VERSION "0.7.6" #define VERSION "0.7.7"
// buffer area defines (in use by godmode.c) // buffer area defines (in use by godmode.c)
#define DIR_BUFFER (0x21000000) #define DIR_BUFFER (0x21000000)

View File

@ -1,5 +1,5 @@
#include "filetype.h" #include "filetype.h"
#include "cia.h" #include "game.h"
#include "ff.h" #include "ff.h"
u32 IdentifyFileType(const char* path) { u32 IdentifyFileType(const char* path) {
@ -36,7 +36,10 @@ u32 IdentifyFileType(const char* path) {
GetCiaInfo(&info, (CiaHeader*) header); GetCiaInfo(&info, (CiaHeader*) header);
if (fsize >= info.size_cia) if (fsize >= info.size_cia)
return GAME_CIA; // CIA file 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; return 0;
} }

View File

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

22
source/game/ncsd.c Normal file
View File

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

30
source/game/ncsd.h Normal file
View File

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

View File

@ -639,7 +639,8 @@ u32 GodMode() {
if (mountable) optionstr[mountable-1] = if (mountable) optionstr[mountable-1] =
(file_type == IMG_NAND) ? "Mount as NAND image" : (file_type == IMG_NAND) ? "Mount as NAND image" :
(file_type == IMG_FAT) ? "Mount as FAT 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"; if (searchdrv) optionstr[searchdrv-1] = "Open containing folder";
u32 user_select = ShowSelectPrompt(n_opt, optionstr, pathstr); u32 user_select = ShowSelectPrompt(n_opt, optionstr, pathstr);

View File

@ -14,10 +14,18 @@
#define NAME_CIA_META "meta.bin" #define NAME_CIA_META "meta.bin"
#define NAME_CIA_CONTENT "%04X.%08lX.app" // index.id.app #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 u32 vgame_type = 0;
static VirtualFile* templates = (VirtualFile*) VGAME_BUFFER; // first 128kb reserved static VirtualFile* templates = (VirtualFile*) VGAME_BUFFER; // first 128kb reserved
static int n_templates = -1; 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 CiaStub* cia = (CiaStub*) (VGAME_BUFFER + 0xF4000); // 48kB reserved - should be enough by far
u32 InitVGameDrive(void) { // prerequisite: game file mounted as image 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) || (GetCiaInfo(&info, &(cia->header)) != 0) ||
(ReadImageBytes((u8*) cia, 0, info.offset_content) != 0)) (ReadImageBytes((u8*) cia, 0, info.offset_content) != 0))
return 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 } else return 0; // not a mounted game file
vgame_type = type; vgame_type = type;
@ -42,6 +54,56 @@ u32 CheckVGameDrive(void) {
return vgame_type; 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) { bool BuildVGameCiaVDir(void) {
CiaInfo info; CiaInfo info;
@ -129,6 +191,7 @@ bool BuildVGameCiaVDir(void) {
} }
bool ReadVGameDir(VirtualFile* vfile, const char* path) { bool ReadVGameDir(VirtualFile* vfile, const char* path) {
(void) path; // not in use yet (void) path; // not in use yet
static int num = -1; static int num = -1;
@ -136,9 +199,11 @@ bool ReadVGameDir(VirtualFile* vfile, const char* path) {
num = -1; // reset dir reader / internal number num = -1; // reset dir reader / internal number
memset(templates, 0, sizeof(VirtualFile) * MAX_N_TEMPLATES); memset(templates, 0, sizeof(VirtualFile) * MAX_N_TEMPLATES);
n_templates = 0; n_templates = 0;
if (!BuildVGameCiaVDir()) // NCCH / NCSD !!! if ((vgame_type == GAME_CIA) && BuildVGameCiaVDir()) // for CIA
return false; return true;
return true; else if ((vgame_type == GAME_NCSD) && BuildVGameNcsdVDir()) // for NCSD
return true;
return false;
} }
if (++num < n_templates) { if (++num < n_templates) {