Added support for mounting NDS images

This commit is contained in:
d0k3 2017-08-14 02:47:00 +02:00
parent fdb93b51f3
commit 32fc283791
8 changed files with 177 additions and 29 deletions

View File

@ -31,7 +31,7 @@
#define FLAG_NUSCDN (1UL<<30)
#define FLAG_CXI (1UL<<31)
#define FTYPE_MOUNTABLE(tp) (tp&(IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|SYS_FIRM|SYS_TICKDB|BIN_KEYDB))
#define FTYPE_MOUNTABLE(tp) (tp&(IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|GAME_NDS|SYS_FIRM|SYS_TICKDB|BIN_KEYDB))
#define FYTPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_BOSS|SYS_FIRM))
#define FYTPE_DECRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|GAME_NUSCDN|SYS_FIRM|BIN_KEYDB))
#define FYTPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|BIN_KEYDB))

View File

@ -92,6 +92,7 @@ bool GetRootDirContentsWorker(DirStruct* contents) {
(GetMountState() & GAME_NCCH ) ? "NCCH" :
(GetMountState() & GAME_EXEFS) ? "EXEFS" :
(GetMountState() & GAME_ROMFS) ? "ROMFS" :
(GetMountState() & GAME_NDS) ? "NDS" :
(GetMountState() & SYS_FIRM) ? "FIRM" : "UNK", drvname[i]);
else snprintf(entry->path + 4, 32, "[%s] %s", drvnum[i], drvname[i]);
entry->name = entry->path + 4;

View File

@ -3,6 +3,19 @@
#define CRC16_TABVAL 0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400
typedef struct {
u32 subtable_offset;
u16 file0_id;
u16 parent_id; // total # of dirs for root entry
} __attribute__((packed)) NitroFntBaseEntry;
typedef struct {
u32 start_address;
u32 end_address;
} __attribute__((packed)) NitroFatEntry;
// see: https://github.com/TASVideos/desmume/blob/master/desmume/src/bios.cpp#L1070tions
u16 crc16_quick(const void* src, u32 len) {
const u16 tabval[] = { CRC16_TABVAL };
@ -75,3 +88,50 @@ u32 GetTwlIcon(u8* icon, const TwlIconData* twl_icon) {
}
return 0;
}
u32 ReadNitroRomDir(u32 dirid, u64* offset, u64* size, bool* is_dir, u8** fnt_entry, TwlHeader* hdr, u8* fnt, u8* fat) {
static u32 fileid = 0;
static u8* subtbl_end = NULL;
NitroFntBaseEntry* fnt_base = (NitroFntBaseEntry*) fnt;
NitroFntBaseEntry* fnt_dir = &((NitroFntBaseEntry*) fnt)[dirid];
NitroFatEntry* fat_lut = (NitroFatEntry*) fat;
if (dirid >= fnt_base->parent_id) return 1; // dir ID out of bounds
if (*fnt_entry && (*fnt_entry - fnt >= (int) hdr->fnt_size)) return 1; // FNT entry out of bounds
if (fnt_base->parent_id*sizeof(NitroFntBaseEntry) > fnt_base->subtable_offset) return 1; // invalid FNT
if (!*fnt_entry) { // if *fnt_entry is NULL: reset file id and start with first entry
*fnt_entry = fnt + fnt_dir->subtable_offset;
fileid = fnt_dir->file0_id;
for (subtbl_end = *fnt_entry; *subtbl_end && (subtbl_end < fnt + hdr->fnt_size); subtbl_end++);
} else { // advance to next entry
u32 pfnlen = **fnt_entry & ~0x80;
bool was_dir = **fnt_entry & 0x80;
*fnt_entry += 1 + pfnlen + (was_dir?2:0);
if (!was_dir) fileid++;
}
// check for trouble
if (*fnt_entry > subtbl_end)
return 1;
// check for end of subtable
if (!**fnt_entry) { // end of subtable reached
*fnt_entry = NULL;
return 0;
}
*is_dir = **fnt_entry & 0x80;
if (!(*is_dir)) { // for files
if (fileid*sizeof(NitroFatEntry) > hdr->fat_size) return 1; // corrupt fnt / fat
if (fat_lut[fileid].start_address > fat_lut[fileid].end_address) return 1; // corrupt fat
*offset = fat_lut[fileid].start_address;
*size = fat_lut[fileid].end_address - fat_lut[fileid].start_address;
} else { // for dirs
u32 fnlen = **fnt_entry & ~0x80;
*offset = (u64) ((*fnt_entry)[1+fnlen]|((*fnt_entry)[1+fnlen+1]<<8)) & 0xFFF; // dir ID goes in offset
*size = 0;
}
return 0;
}

View File

@ -127,3 +127,4 @@ u32 ValidateTwlHeader(TwlHeader* twl);
u32 LoadTwlMetaData(const char* path, TwlHeader* hdr, TwlIconData* icon);
u32 GetTwlTitle(char* desc, const TwlIconData* twl_icon);
u32 GetTwlIcon(u8* icon, const TwlIconData* twl_icon);
u32 ReadNitroRomDir(u32 dirid, u64* offset, u64* size, bool* is_dir, u8** fnt_entry, TwlHeader* hdr, u8* fnt, u8* fat);

View File

@ -3,6 +3,8 @@
#include "game.h"
#include "aes.h"
#define VFLAG_NITRO (1UL<<20)
#define VFLAG_NDS (1UL<<21)
#define VFLAG_FIRM_SECTION (1UL<<22)
#define VFLAG_FIRM_ARM9 (1UL<<23)
#define VFLAG_FIRM (1UL<<24)
@ -13,7 +15,7 @@
#define VFLAG_NCCH (1UL<<29)
#define VFLAG_EXEFS (1UL<<30)
#define VFLAG_ROMFS (1UL<<31)
#define VFLAG_GAMEDIR (VFLAG_FIRM|VFLAG_CIA|VFLAG_NCSD|VFLAG_NCCH|VFLAG_EXEFS|VFLAG_ROMFS|VFLAG_LV3)
#define VFLAG_GAMEDIR (VFLAG_FIRM|VFLAG_CIA|VFLAG_NCSD|VFLAG_NCCH|VFLAG_EXEFS|VFLAG_ROMFS|VFLAG_LV3|VFLAG_NDS|VFLAG_NITRO)
#define NAME_FIRM_HEADER "header.bin"
#define NAME_FIRM_ARM9BIN "arm9dec.bin"
@ -70,14 +72,17 @@ static u64 offset_exefs = (u64) -1;
static u64 offset_romfs = (u64) -1;
static u64 offset_lv3 = (u64) -1;
static u64 offset_lv3fd = (u64) -1;
static u64 offset_nds = (u64) -1;
static CiaStub* cia = (CiaStub*) (void*) (VGAME_BUFFER + 0x10000); // 61.5kB reserved - should be enough by far
static CiaStub* cia = (CiaStub*) (void*) (VGAME_BUFFER + 0x10000); // 61kB reserved - should be enough by far
static TwlHeader* twl = (TwlHeader*) (void*) (VGAME_BUFFER + 0x1F400); // 512 byte reserved (not the full thing)
static FirmA9LHeader* a9l = (FirmA9LHeader*) (void*) (VGAME_BUFFER + 0x1F600); // 512 byte reserved
static FirmHeader* firm = (FirmHeader*) (void*) (VGAME_BUFFER + 0x1F800); // 512 byte reserved
static NcsdHeader* ncsd = (NcsdHeader*) (void*) (VGAME_BUFFER + 0x1FA00); // 512 byte reserved
static NcchHeader* ncch = (NcchHeader*) (void*) (VGAME_BUFFER + 0x1FC00); // 512 byte reserved
static ExeFsHeader* exefs = (ExeFsHeader*) (void*) (VGAME_BUFFER + 0x1FE00); // 512 byte reserved
static u8* romfslv3 = (u8*) (VGAME_BUFFER + 0x20000); // 1920kB reserved
static u8* nitrofs = (u8*) (VGAME_BUFFER + 0x20000); // 1920kB reserved (FNT+FAT combined)
static RomFsLv3Index lv3idx;
int ReadFirmImageBytes(void* buffer, u64 offset, u64 count) {
@ -331,20 +336,21 @@ bool BuildVGameCiaDir(void) {
u64 next_offset = info.offset_content;
for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++) {
u64 size = getbe64(content_list[i].size);
bool is_ncch = false; // is unencrypted NCCH?
if (!(getbe16(content_list[i].type) & 0x1) && (size >= sizeof(NcchHeader))) {
NcchHeader ncch;
ReadImageBytes((u8*) &ncch, next_offset, sizeof(NcchHeader));
is_ncch = (ValidateNcchHeader(&ncch) == 0);
u32 cnt_type = 0;
if (!(getbe16(content_list[i].type) & 0x1) && (size >= 0x200)) {
u8 header[0x200];
ReadImageBytes(header, next_offset, 0x200);
cnt_type = (ValidateNcchHeader((NcchHeader*)(void*)header) == 0) ? VFLAG_NCCH :
(ValidateTwlHeader((TwlHeader*)(void*)header) == 0) ? VFLAG_NDS : 0;
}
snprintf(templates[n].name, 32, NAME_CIA_CONTENT,
getbe16(content_list[i].index), getbe32(content_list[i].id), ".app");
templates[n].offset = next_offset;
templates[n].size = size;
templates[n].keyslot = 0xFF; // even for encrypted stuff
templates[n].flags = is_ncch ? VFLAG_NCCH : 0;
templates[n].flags = cnt_type;
n++;
if (is_ncch) {
if (cnt_type) {
memcpy(templates + n, templates + n - 1, sizeof(VirtualFile));
snprintf(templates[n].name, 32, NAME_CIA_CONTENT,
getbe16(content_list[i].index), getbe32(content_list[i].id), "");
@ -464,6 +470,7 @@ u32 InitVGameDrive(void) { // prerequisite: game file mounted as image
offset_romfs = (u64) -1;
offset_lv3 = (u64) -1;
offset_lv3fd = (u64) -1;
offset_nds = (u64) -1;
base_vdir =
(type & SYS_FIRM ) ? VFLAG_FIRM :
@ -471,7 +478,8 @@ u32 InitVGameDrive(void) { // prerequisite: game file mounted as image
(type & GAME_NCSD ) ? VFLAG_NCSD :
(type & GAME_NCCH ) ? VFLAG_NCCH :
(type & GAME_EXEFS) ? VFLAG_EXEFS :
(type & GAME_ROMFS) ? VFLAG_ROMFS : 0;
(type & GAME_ROMFS) ? VFLAG_ROMFS :
(type & GAME_NDS ) ? VFLAG_NDS : 0;
if (!base_vdir) return 0;
vgame_type = type;
@ -546,6 +554,7 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) {
offset_exefs = vdir->offset;
if (!BuildVGameExeFsDir()) return false;
} else if ((vdir->flags & VFLAG_ROMFS) && (offset_romfs != vdir->offset)) {
offset_nds = (u64) -1; // mutually exclusive
// validate romFS magic
u8 magic[] = { ROMFS_MAGIC };
u8 header[sizeof(magic)];
@ -567,21 +576,37 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) {
offset_lv3fd = offset_lv3 + lv3->offset_filedata;
offset_romfs = vdir->offset;
BuildLv3Index(&lv3idx, romfslv3);
} else if ((vdir->flags & VFLAG_NDS) && (offset_nds != vdir->offset)) {
offset_romfs = (u64) -1; // mutually exclusive
// validate NDS header
if ((ReadImageBytes(twl, vdir->offset, 0x200) != 0) ||
(ValidateTwlHeader(twl) != 0) ||
(twl->fnt_offset >= twl->fat_offset))
return false;
// load NitroFNT & NitroFAT to memory
u32 offset_nitro = twl->fnt_offset;
u32 size_nitro = (twl->fat_offset + twl->fat_size) - offset_nitro;
if ((size_nitro > VGAME_BUFFER_SIZE - 0x20000) ||
(ReadImageBytes(nitrofs, vdir->offset + offset_nitro, size_nitro) != 0))
return 1;
offset_nds = vdir->offset;
}
// for romfs dir: switch to lv3 dir object
if (vdir->flags & VFLAG_ROMFS) {
// for romfs/nds dir: switch to lv3/nitro dir object
if (vdir->flags & (VFLAG_ROMFS|VFLAG_NDS)) {
vdir->index = -1;
vdir->offset = 0;
vdir->size = 0;
vdir->flags &= ~VFLAG_ROMFS;
vdir->flags |= VFLAG_LV3;
if (vdir->flags & VFLAG_ROMFS) vdir->flags |= VFLAG_LV3;
if (vdir->flags & VFLAG_NDS) vdir->flags |= VFLAG_NITRO;
vdir->flags &= ~(VFLAG_ROMFS|VFLAG_NDS);
}
return true; // error (should not happen)
return true;
}
bool ReadVGameDirLv3(VirtualFile* vfile, VirtualDir* vdir) {
vfile->name[0] = '\0';
BuildLv3Index(&lv3idx, romfslv3);
vfile->flags = VFLAG_LV3;
vfile->keyslot = ((offset_ncch != (u64) -1) && NCCH_ENCRYPTED(ncch)) ?
@ -653,6 +678,36 @@ bool ReadVGameDirLv3(VirtualFile* vfile, VirtualDir* vdir) {
return false;
}
bool ReadVGameDirNitro(VirtualFile* vfile, VirtualDir* vdir) {
static u8* fnt_entry = NULL;
vfile->name[0] = '\0';
vfile->flags = VFLAG_NITRO;
vfile->keyslot = 0;
// start from parent dir object
if (vdir->index == -1) {
fnt_entry = NULL;
vdir->index = 0;
}
// read directory entries until done
if (vdir->index == 0) {
u8* fnt = nitrofs;
u8* fat = nitrofs + twl->fat_offset - twl->fnt_offset;
u32 dirid = vdir->offset & 0xFFF;
bool is_dir;
if (ReadNitroRomDir(dirid, &(vfile->offset), &(vfile->size), &is_dir, &fnt_entry, twl, fnt, fat) != 0)
vdir->index = 2; // error reading dir
if (fnt_entry) {
if (!is_dir) vfile->offset += offset_nds;
vfile->offset |= ((u64)(fnt_entry - fnt)) << 32;
if (is_dir) vfile->flags |= VFLAG_DIR;
} else vdir->index = 1; // end of dir
}
return (vdir->index == 0);
}
bool ReadVGameDir(VirtualFile* vfile, VirtualDir* vdir) {
VirtualFile* templates;
int n = 0;
@ -674,6 +729,8 @@ bool ReadVGameDir(VirtualFile* vfile, VirtualDir* vdir) {
n = n_templates_exefs;
} else if (vdir->flags & VFLAG_LV3) {
return ReadVGameDirLv3(vfile, vdir);
} else if (vdir->flags & VFLAG_NITRO) {
return ReadVGameDirNitro(vfile, vdir);
}
if (++vdir->index < n) {
@ -692,6 +749,8 @@ int ReadVGameFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 count)
if (vfile->flags & VFLAG_DIR) return -1;
lv3file = LV3_GET_FILE(vfile->offset, &lv3idx);
vfoffset = offset_lv3fd + lv3file->offset_data;
} else if (vfile->flags & VFLAG_NITRO) {
vfoffset = vfile->offset & 0xFFFFFFFF;
}
if (vfile->flags & (VFLAG_EXEFS_FILE|VFLAG_EXTHDR|VFLAG_EXEFS|VFLAG_ROMFS|VFLAG_LV3|VFLAG_NCCH))
return ReadNcchImageBytes(buffer, vfoffset + offset, count);
@ -749,10 +808,36 @@ bool GetVGameLv3Filename(char* name, const VirtualFile* vfile, u32 n_chars) {
return true;
}
bool MatchVGameLv3Filename(const char* name, const VirtualFile* vfile, u32 n_chars) {
bool GetVGameNitroFilename(char* name, const VirtualFile* vfile, u32 n_chars) {
if (!(vfile->flags & VFLAG_NITRO))
return false;
u8* fnt_entry = nitrofs + (vfile->offset >> 32);
u32 name_len = (*fnt_entry) & ~0x80;
if (name_len >= n_chars) return false;
memset(name, 0, n_chars);
memcpy(name, fnt_entry + 1, name_len);
return true;
}
bool GetVGameFilename(char* name, const VirtualFile* vfile, u32 n_chars) {
if (vfile->flags & VFLAG_LV3) return GetVGameLv3Filename(name, vfile, n_chars);
else if (vfile->flags & VFLAG_NITRO) return GetVGameNitroFilename(name, vfile, n_chars);
else strncpy(name, vfile->name, n_chars);
return true;
}
bool MatchVGameFilename(const char* name, const VirtualFile* vfile, u32 n_chars) {
if (vfile->flags & VFLAG_LV3) {
char lv3_name[256];
if (!GetVGameLv3Filename(lv3_name, vfile, 256)) return false;
return (strncasecmp(name, lv3_name, n_chars) == 0);
} else if (vfile->flags & VFLAG_NITRO) {
char nitro_name[128];
if (!GetVGameNitroFilename(nitro_name, vfile, 128)) return false;
return (strncasecmp(name, nitro_name, n_chars) == 0);
} else return (strncasecmp(name, vfile->name, 32) == 0);
}
u64 GetVGameDriveSize(void) {

View File

@ -13,7 +13,7 @@ int ReadVGameFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 count)
// int WriteVGameFile(const VirtualFile* vfile, const void* buffer, u64 offset, u64 count); // writing is not enabled
bool FindVirtualFileInLv3Dir(VirtualFile* vfile, const VirtualDir* vdir, const char* name);
bool GetVGameLv3Filename(char* name, const VirtualFile* vfile, u32 n_chars);
bool MatchVGameLv3Filename(const char* name, const VirtualFile* vfile, u32 n_chars);
bool GetVGameFilename(char* name, const VirtualFile* vfile, u32 n_chars);
bool MatchVGameFilename(const char* name, const VirtualFile* vfile, u32 n_chars);
u64 GetVGameDriveSize(void);

View File

@ -111,8 +111,8 @@ bool GetVirtualFile(VirtualFile* vfile, const char* path) {
if (!(vdir.flags & VFLAG_LV3)) { // standard method
while (true) {
if (!ReadVirtualDir(vfile, &vdir)) return false;
if ((!(vfile->flags & VFLAG_LV3) && (strncasecmp(name, vfile->name, 32) == 0)) ||
((vfile->flags & VFLAG_LV3) && MatchVGameLv3Filename(name, vfile, 256)))
if ((!(vfile->flags & VRT_GAME) && (strncasecmp(name, vfile->name, 32) == 0)) ||
((vfile->flags & VRT_GAME) && MatchVGameFilename(name, vfile, 256)))
break; // entry found
}
} else { // use lv3 hashes for quicker search
@ -132,8 +132,8 @@ bool GetVirtualDir(VirtualDir* vdir, const char* path) {
}
bool GetVirtualFilename(char* name, const VirtualFile* vfile, u32 n_chars) {
if (!(vfile->flags & VFLAG_LV3)) strncpy(name, vfile->name, n_chars);
else if (!GetVGameLv3Filename(name, vfile, n_chars)) return false;
if (!(vfile->flags & VRT_GAME)) strncpy(name, vfile->name, n_chars);
else if (!GetVGameFilename(name, vfile, n_chars)) return false;
return true;
}

View File

@ -19,14 +19,15 @@
#define VFLAG_ROOT (1UL<<17)
#define VFLAG_LV3 (1UL<<18)
#define VRT_DRIVES {'S', VRT_SYSNAND}, {'E', VRT_EMUNAND}, {'I', VRT_IMGNAND}, {'X', VRT_XORPAD }, \
{'M', VRT_MEMORY}, {'G', VRT_GAME}, {'K', VRT_KEYDB}, {'T', VRT_TICKDB}, {'C', VRT_CART}
// 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...21: reserved for external flags
// bits 22...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];
u64 offset; // must be a multiple of 0x200 (for NAND access)