diff --git a/source/game/exefs.c b/source/game/exefs.c new file mode 100644 index 0000000..0a5e09f --- /dev/null +++ b/source/game/exefs.c @@ -0,0 +1,16 @@ +#include "exefs.h" + +u32 ValidateExeFsHeader(ExeFsHeader* exefs, u32 size) { + u32 data_size = 0; + for (u32 i = 0; i < 10; i++) { + ExeFsFileHeader* file = exefs->files + i; + if ((file->offset == 0) && (file->size == 0)) + continue; + if (file->offset < data_size) + return 1; // overlapping data, failed + data_size = file->offset + file->size; + } + if (size && (data_size > (size - sizeof(ExeFsHeader)))) // exefs header not included in table + return 1; + return 0; +} diff --git a/source/game/exefs.h b/source/game/exefs.h new file mode 100644 index 0000000..c86b4dd --- /dev/null +++ b/source/game/exefs.h @@ -0,0 +1,18 @@ +#pragma once + +#include "common.h" + +typedef struct { + char name[8]; + u32 offset; + u32 size; +} __attribute__((packed)) ExeFsFileHeader; + +// see: https://www.3dbrew.org/wiki/ExeFS +typedef struct { + ExeFsFileHeader files[10]; + u8 reserved[0x20]; + u8 hashes[10][0x20]; +} __attribute__((packed)) ExeFsHeader; + +u32 ValidateExeFsHeader(ExeFsHeader* exefs, u32 size); diff --git a/source/game/game.h b/source/game/game.h index 51aff25..2f46fcc 100644 --- a/source/game/game.h +++ b/source/game/game.h @@ -4,3 +4,4 @@ #include "cia.h" #include "ncsd.h" #include "ncch.h" +#include "exefs.h" diff --git a/source/virtual/vgame.c b/source/virtual/vgame.c index 80f7b01..333ffbd 100644 --- a/source/virtual/vgame.c +++ b/source/virtual/vgame.c @@ -4,10 +4,20 @@ #include "aes.h" #include "ff.h" -#define VFLAG_EXTHDR (1<<29) +#define VFLAG_EXTHDR (1<<26) +#define VFLAG_CIA (1<<27) // unused, see below +#define VFLAG_NCSD (1<<28) // unused, see below +#define VFLAG_NCCH (1<<29) #define VFLAG_EXEFS (1<<30) #define VFLAG_ROMFS (1<<31) +#define VDIR_CIA VFLAG_CIA +#define VDIR_NCSD VFLAG_NCSD +#define VDIR_NCCH VFLAG_NCCH +#define VDIR_EXEFS VFLAG_EXEFS +#define VDIR_ROMFS VFLAG_ROMFS +#define VDIR_GAME (VDIR_CIA|VDIR_NCSD|VDIR_NCCH|VDIR_EXEFS|VDIR_ROMFS) + #define MAX_N_TEMPLATES 2048 // this leaves us with enough room (128kB reserved) #define NAME_CIA_HEADER "header.bin" @@ -17,6 +27,7 @@ #define NAME_CIA_TMDCHUNK "tmdchunks.bin" #define NAME_CIA_META "meta.bin" #define NAME_CIA_CONTENT "%04X.%08lX.app" // index.id.app +#define NAME_CIA_DIR "%04X.%08lX" // index.id #define NAME_NCSD_HEADER "ncsd.bin" #define NAME_NCSD_CARDINFO "cardinfo.bin" @@ -24,6 +35,9 @@ #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" +#define NAME_NCSD_DIR "cnt0.game", "cnt1.manual", "cnt2.dlp", \ + "cnt3", "cnt4", "cnt5", \ + "cnt6.update_n3ds", "cnt7.update_o3ds" #define NAME_NCCH_HEADER "ncch.bin" #define NAME_NCCH_EXTHEADER "extheader.bin" @@ -33,21 +47,254 @@ #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 u32 base_vdir = 0; -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 VirtualFile* templates_cia = (VirtualFile*) VGAME_BUFFER; // first 116kb reserved (enough for ~2000 entries) +static VirtualFile* templates_ncsd = (VirtualFile*) VGAME_BUFFER + 0x1D000; // 4kb reserved (enough for ~80 entries) +static VirtualFile* templates_ncch = (VirtualFile*) VGAME_BUFFER + 0x1E000; // 4kb reserved (enough for ~80 entries) +static VirtualFile* templates_exefs = (VirtualFile*) VGAME_BUFFER + 0x1F000; // 4kb reserved (enough for ~80 entries) +static int n_templates_cia = -1; +static int n_templates_ncsd = -1; +static int n_templates_ncch = -1; +static int n_templates_exefs = -1; static u32 offset_ncch = 0; static u32 offset_exefs = 0; static u32 offset_romfs = 0; +static CiaStub* cia = (CiaStub*) (VGAME_BUFFER + 0xF4000); // 48kB reserved - should be enough by far +static NcsdHeader* ncsd = (NcsdHeader*) (VGAME_BUFFER + 0xF3000); // 512 byte reserved +static NcchHeader* ncch = (NcchHeader*) (VGAME_BUFFER + 0xF3200); // 512 byte reserved +static ExeFsHeader* exefs = (ExeFsHeader*) (VGAME_BUFFER + 0xF3400); // 512 byte reserved + +bool BuildVGameNcchDir(void) { + VirtualFile* templates = templates_ncch; + u32 n = 0; + + // header + strncpy(templates[n].name, NAME_NCCH_HEADER, 32); + templates[n].offset = offset_ncch + 0; + templates[n].size = 0x200; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + + // extended header + if (ncch->size_exthdr) { + strncpy(templates[n].name, NAME_NCCH_EXTHEADER, 32); + templates[n].offset = offset_ncch + NCCH_EXTHDR_OFFSET; + templates[n].size = NCCH_EXTHDR_SIZE; + templates[n].keyslot = 0xFF; // crypto ? + templates[n].flags = 0; + n++; + } + + // plain region + if (ncch->size_plain) { + strncpy(templates[n].name, NAME_NCCH_PLAIN, 32); + templates[n].offset = offset_ncch + (ncch->offset_plain * NCCH_MEDIA_UNIT); + templates[n].size = ncch->size_plain * NCCH_MEDIA_UNIT; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + } + + // logo region + if (ncch->size_logo) { + strncpy(templates[n].name, NAME_NCCH_LOGO, 32); + templates[n].offset =offset_ncch + (ncch->offset_logo * NCCH_MEDIA_UNIT); + templates[n].size = ncch->size_logo * NCCH_MEDIA_UNIT; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + } + + // exefs + if (ncch->size_exefs) { + strncpy(templates[n].name, NAME_NCCH_EXEFS, 32); + templates[n].offset = offset_ncch + (ncch->offset_exefs * NCCH_MEDIA_UNIT); + templates[n].size = ncch->size_exefs * NCCH_MEDIA_UNIT; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + } + + // romfs + if (ncch->size_romfs) { + strncpy(templates[n].name, NAME_NCCH_ROMFS, 32); + templates[n].offset = offset_ncch + (ncch->offset_romfs * NCCH_MEDIA_UNIT); + templates[n].size = ncch->size_romfs * NCCH_MEDIA_UNIT; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + } + + n_templates_ncch = n; + return true; +} + +bool BuildVGameNcsdDir(void) { + const char* name_content[] = { NAME_NCSD_CONTENT }; + const char* name_dir[] = { NAME_NCSD_DIR }; + VirtualFile* templates = templates_ncsd; + u32 n = 0; + + // header + strncpy(templates[n].name, NAME_NCSD_HEADER, 32); + templates[n].offset = 0; + templates[n].size = 0x200; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + + // card info header + if (ncsd->partitions[0].offset * NCSD_MEDIA_UNIT >= NCSD_CINFO_OFFSET + NCSD_CINFO_SIZE) { + strncpy(templates[n].name, NAME_NCSD_CARDINFO, 32); + templates[n].offset = NCSD_CINFO_OFFSET; + templates[n].size = NCSD_CINFO_SIZE; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + } + + // dev info header + if (ncsd->partitions[0].offset * NCSD_MEDIA_UNIT >= NCSD_DINFO_OFFSET + NCSD_DINFO_SIZE) { + strncpy(templates[n].name, NAME_NCSD_DEVINFO, 32); + templates[n].offset = NCSD_DINFO_OFFSET; + templates[n].size = NCSD_DINFO_SIZE; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + } + + // contents + for (u32 i = 0; i < 8; i++) { + NcchPartition* partition = ncsd->partitions + i; + if ((partition->offset == 0) && (partition->size == 0)) + continue; + strncpy(templates[n].name, name_content[i], 32); + templates[n].offset = partition->offset * NCSD_MEDIA_UNIT; + templates[n].size = partition->size * NCSD_MEDIA_UNIT; + templates[n].keyslot = 0xFF; // not encrypted + templates[n].flags = VFLAG_NCCH; + n++; + memcpy(templates + n, templates + n - 1, sizeof(VirtualFile)); + strncpy(templates[n].name, name_dir[i], 32); + templates[n].flags |= VFLAG_DIR; + n++; + } + + n_templates_ncsd = n; + return true; +} +#include "ui.h" +bool BuildVGameCiaDir(void) { + CiaInfo info; + VirtualFile* templates = templates_cia; + u32 n = 0; + + if (GetCiaInfo(&info, &(cia->header)) != 0) + return false; + + // header + strncpy(templates[n].name, NAME_CIA_HEADER, 32); + templates[n].offset = 0; + templates[n].size = info.size_header; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + + // certificates + if (info.size_cert) { + strncpy(templates[n].name, NAME_CIA_CERT, 32); + templates[n].offset = info.offset_cert; + templates[n].size = info.size_cert; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + } + + // ticket + if (info.size_ticket) { + strncpy(templates[n].name, NAME_CIA_TICKET, 32); + templates[n].offset = info.offset_ticket; + templates[n].size = info.size_ticket; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + } + + // TMD (the full thing) + if (info.size_tmd) { + strncpy(templates[n].name, NAME_CIA_TMD, 32); + templates[n].offset = info.offset_tmd; + templates[n].size = info.size_tmd; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + } + + // TMD content chunks + if (info.size_content_list) { + strncpy(templates[n].name, NAME_CIA_TMDCHUNK, 32); + templates[n].offset = info.offset_content_list; + templates[n].size = info.size_content_list; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + } + + // meta + if (info.size_meta) { + strncpy(templates[n].name, NAME_CIA_META, 32); + templates[n].offset = info.offset_meta; + templates[n].size = info.size_meta; + templates[n].keyslot = 0xFF; + templates[n].flags = 0; + n++; + } + + // contents + if (info.size_content) { + TmdContentChunk* content_list = cia->content_list; + u32 content_count = getbe16(cia->tmd.content_count); + u64 next_offset = info.offset_content; + for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) { + u64 size = getbe64(content_list[i].size); + bool is_ncch = false; // is unencrypted NCCH? + if (!(getbe16(content_list[i].type) & 0x1)) { + NcchHeader ncch; + ReadImageBytes((u8*) &ncch, next_offset, sizeof(NcchHeader)); + is_ncch = (ValidateNcchHeader(&ncch) == 0); + } + snprintf(templates[n].name, 32, NAME_CIA_CONTENT, + getbe16(content_list[i].index), getbe32(content_list[i].id)); + 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; + n++; + if (is_ncch) { + memcpy(templates + n, templates + n - 1, sizeof(VirtualFile)); + snprintf(templates[n].name, 32, NAME_CIA_DIR, + getbe16(content_list[i].index), getbe32(content_list[i].id)); + templates[n].flags |= VFLAG_DIR; + n++; + } + next_offset += size; + } + } + + n_templates_cia = n; + return true; +} + u32 InitVGameDrive(void) { // prerequisite: game file mounted as image u32 type = GetMountState(); vgame_type = 0; + offset_ncch = 0; + offset_exefs = 0; + offset_romfs = 0; if (type == GAME_CIA) { // for CIAs: load the CIA stub and keep it in memory CiaInfo info; if ((ReadImageBytes((u8*) cia, 0, 0x20) != 0) || @@ -55,15 +302,23 @@ u32 InitVGameDrive(void) { // prerequisite: game file mounted as image (GetCiaInfo(&info, &(cia->header)) != 0) || (ReadImageBytes((u8*) cia, 0, info.offset_content) != 0)) return 0; + if (!BuildVGameCiaDir()) return 0; + base_vdir = VDIR_CIA; } else if (type == GAME_NCSD) { if ((ReadImageBytes((u8*) ncsd, 0, sizeof(NcsdHeader)) != 0) || (ValidateNcsdHeader(ncsd) != 0)) return 0; + if (!BuildVGameNcsdDir()) return 0; + base_vdir = VDIR_NCSD; } else if (type == GAME_NCCH) { - offset_ncch = 0; if ((ReadImageBytes((u8*) ncch, 0, sizeof(NcchHeader)) != 0) || (ValidateNcchHeader(ncch) != 0)) return 0; + if (!BuildVGameNcchDir()) return 0; + base_vdir = VDIR_NCCH; + offset_ncch = 0; + offset_exefs = ncch->offset_exefs * NCCH_MEDIA_UNIT; + offset_romfs = ncch->offset_romfs * NCCH_MEDIA_UNIT; } else return 0; // not a mounted game file vgame_type = type; @@ -75,228 +330,61 @@ 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 }; - - 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 >= NCSD_CINFO_OFFSET + NCSD_CINFO_SIZE) { - strncpy(templates[n_templates].name, NAME_NCSD_CARDINFO, 32); - 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 >= NCSD_DINFO_OFFSET + NCSD_DINFO_SIZE) { - strncpy(templates[n_templates].name, NAME_NCSD_DEVINFO, 32); - 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++; - } - - // 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; - - if ((CheckVGameDrive() != GAME_CIA) || (GetCiaInfo(&info, &(cia->header)) != 0)) - return false; // safety check - - // header - strncpy(templates[n_templates].name, NAME_CIA_HEADER, 32); - templates[n_templates].offset = 0; - templates[n_templates].size = info.size_header; - templates[n_templates].keyslot = 0xFF; - templates[n_templates].flags = 0; - n_templates++; - - // certificates - if (info.size_cert) { - strncpy(templates[n_templates].name, NAME_CIA_CERT, 32); - templates[n_templates].offset = info.offset_cert; - templates[n_templates].size = info.size_cert; - templates[n_templates].keyslot = 0xFF; - templates[n_templates].flags = 0; - n_templates++; - } - - // ticket - if (info.size_ticket) { - strncpy(templates[n_templates].name, NAME_CIA_TICKET, 32); - templates[n_templates].offset = info.offset_ticket; - templates[n_templates].size = info.size_ticket; - templates[n_templates].keyslot = 0xFF; - templates[n_templates].flags = 0; - n_templates++; - } - - // TMD (the full thing) - if (info.size_tmd) { - strncpy(templates[n_templates].name, NAME_CIA_TMD, 32); - templates[n_templates].offset = info.offset_tmd; - templates[n_templates].size = info.size_tmd; - templates[n_templates].keyslot = 0xFF; - templates[n_templates].flags = 0; - n_templates++; - } - - // TMD content chunks - if (info.size_content_list) { - strncpy(templates[n_templates].name, NAME_CIA_TMDCHUNK, 32); - templates[n_templates].offset = info.offset_content_list; - templates[n_templates].size = info.size_content_list; - templates[n_templates].keyslot = 0xFF; - templates[n_templates].flags = 0; - n_templates++; - } - - // meta - if (info.size_meta) { - strncpy(templates[n_templates].name, NAME_CIA_META, 32); - templates[n_templates].offset = info.offset_meta; - templates[n_templates].size = info.size_meta; - templates[n_templates].keyslot = 0xFF; - templates[n_templates].flags = 0; - n_templates++; - } - - // contents - if (info.size_content) { - TmdContentChunk* content_list = cia->content_list; - u32 content_count = getbe16(cia->tmd.content_count); - u64 next_offset = info.offset_content; - for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) { - u64 size = getbe64(content_list[i].size); - // bool encrypted = getbe16(content_list[i].type) & 0x1; - snprintf(templates[n_templates].name, 32, NAME_CIA_CONTENT, - getbe16(content_list[i].index), getbe32(content_list[i].id)); - templates[n_templates].offset = (u32) next_offset; - templates[n_templates].size = (u32) size; - templates[n_templates].keyslot = 0xFF; // even for encrypted stuff - templates[n_templates].flags = 0; // this handles encryption - n_templates++; - next_offset += size; +bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) { + vdir->index = -1; + vdir->virtual_src = VRT_GAME; + if (!ventry) { // root dir + vdir->offset = 0; + vdir->size = GetMountSize(); + vdir->flags = VFLAG_DIR|base_vdir; + // base dir is already in memory -> done + } else { // non root dir + if (!(ventry->flags & VFLAG_DIR) || !(ventry->flags & VDIR_GAME)) + return false; + vdir->offset = ventry->offset; + vdir->size = ventry->size; + vdir->flags = ventry->flags; + // build directories where required + u32 curr_vdir = vdir->flags & VDIR_GAME; + if ((curr_vdir == VDIR_NCCH) && (offset_ncch != vdir->offset)) { + if ((ReadImageBytes((u8*) ncch, vdir->offset, sizeof(NcchHeader)) != 0) || + (ValidateNcchHeader(ncch) != 0)) + return false; + offset_ncch = vdir->offset; + if (!BuildVGameNcchDir()) return false; + } else if ((curr_vdir == VDIR_EXEFS) && (offset_exefs != vdir->offset)) { + if ((ReadImageBytes((u8*) exefs, vdir->offset, sizeof(ExeFsHeader)) != 0) || + (ValidateExeFsHeader(exefs, ncch->size_exefs * NCCH_MEDIA_UNIT) != 0)) + return false; + offset_exefs = vdir->offset; } } - return true; + return true; // error (should not happen) } -bool ReadVGameDir(VirtualFile* vfile, const char* path) { +bool ReadVGameDir(VirtualFile* vfile, VirtualDir* vdir) { + u32 curr_vdir = vdir->flags & VDIR_GAME; + VirtualFile* templates; + int n = 0; - (void) path; // not in use yet - static int num = -1; - - if (!vfile) { // NULL pointer - num = -1; // reset dir reader / internal number - memset(templates, 0, sizeof(VirtualFile) * MAX_N_TEMPLATES); - n_templates = 0; - if ((vgame_type == GAME_CIA) && BuildVGameCiaVDir()) // for CIA - 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; + if (curr_vdir == VDIR_CIA) { + templates = templates_cia; + n = n_templates_cia; + } else if (curr_vdir == VDIR_NCSD) { + templates = templates_ncsd; + n = n_templates_ncsd; + } else if (curr_vdir == VDIR_NCCH) { + templates = templates_ncch; + n = n_templates_ncch; + } else if (curr_vdir == VDIR_EXEFS) { + templates = templates_exefs; + n = n_templates_exefs; } - if (++num < n_templates) { + if (++vdir->index < n) { // copy current template to vfile - memcpy(vfile, templates + num, sizeof(VirtualFile)); + memcpy(vfile, templates + vdir->index, sizeof(VirtualFile)); return true; } diff --git a/source/virtual/vgame.h b/source/virtual/vgame.h index dce6fcd..300e49d 100644 --- a/source/virtual/vgame.h +++ b/source/virtual/vgame.h @@ -7,6 +7,7 @@ u32 InitVGameDrive(void); u32 CheckVGameDrive(void); -bool ReadVGameDir(VirtualFile* vfile, const char* path); +bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry); +bool ReadVGameDir(VirtualFile* vfile, VirtualDir* vdir); int ReadVGameFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count); // int WriteVGameFile(const VirtualFile* vfile, const u8* buffer, u32 offset, u32 count); // writing is not enabled diff --git a/source/virtual/virtual.c b/source/virtual/virtual.c index 9e84fac..d4dc023 100644 --- a/source/virtual/virtual.c +++ b/source/virtual/virtual.c @@ -30,52 +30,91 @@ bool CheckVirtualDrive(const char* path) { return virtual_src; // this is safe for SysNAND & memory } -bool ReadVirtualDir(VirtualFile* vfile, u32 virtual_src) { +bool ReadVirtualDir(VirtualFile* vfile, VirtualDir* vdir) { + u32 virtual_src = vdir->virtual_src; + bool ret = false; if (virtual_src & (VRT_SYSNAND|VRT_EMUNAND|VRT_IMGNAND)) { - return ReadVNandDir(vfile, virtual_src); + ret = ReadVNandDir(vfile, vdir); } else if (virtual_src & VRT_MEMORY) { - return ReadVMemDir(vfile); - } else if (virtual_src & VRT_MEMORY) { - return ReadVMemDir(vfile); + ret = ReadVMemDir(vfile, vdir); } else if (virtual_src & VRT_GAME) { - return ReadVGameDir(vfile, NULL); + ret = ReadVGameDir(vfile, vdir); } - return false; + vfile->flags |= virtual_src; // add source flag + return ret; +} + +bool OpenVirtualRoot(VirtualDir* vdir, u32 virtual_src) { + if (virtual_src & VRT_GAME) { + if (!OpenVGameDir(vdir, NULL)) return false; + } else { // generic vdir object + vdir->offset = 0; + vdir->size = 0; + vdir->flags = 0; + } + vdir->index = -1; + vdir->flags |= VFLAG_DIR|virtual_src; + vdir->virtual_src = virtual_src; + return true; +} + +bool OpenVirtualDir(VirtualDir* vdir, VirtualFile* ventry) { + u32 virtual_src = ventry->flags & VRT_SOURCE; + if (ventry->flags & VFLAG_ROOT) + return OpenVirtualRoot(vdir, virtual_src); + if (!(virtual_src & VRT_GAME)) return false; // no subdirs in other virtual sources + if (!OpenVGameDir(vdir, ventry)) return false; + vdir->flags |= virtual_src; + vdir->virtual_src = virtual_src; + return true; } bool GetVirtualFile(VirtualFile* vfile, const char* path) { - // get / fix the name - char* fname = strchr(path, '/'); - if (!fname) return false; - fname++; + char lpath[256]; + strncpy(lpath, path, 256); - // check path validity / get virtual source + // get virtual source / root dir object u32 virtual_src = 0; virtual_src = GetVirtualSource(path); - if (!virtual_src || (fname - path != 3)) - return false; + if (!virtual_src) return false; - // read virtual dir, match the path / size - ReadVirtualDir(NULL, virtual_src); // reset dir reader - while (ReadVirtualDir(vfile, virtual_src)) { - vfile->flags |= virtual_src; // add source flag - if (strncasecmp(fname, vfile->name, 32) == 0) - return true; // file found + // set vfile as root object + memset(vfile, 0, sizeof(VirtualDir)); + vfile->flags = VFLAG_ROOT|virtual_src; + if (strnlen(lpath, 256) <= 3) return true; + + // tokenize / parse path + char* name; + VirtualDir vdir; + if (!OpenVirtualRoot(&vdir, virtual_src)) return false; + for (name = strtok(lpath + 3, "/"); name && vdir.virtual_src; name = strtok(NULL, "/")) { + while (true) { + if (!ReadVirtualDir(vfile, &vdir)) return false; + if (strncasecmp(name, vfile->name, 32) == 0) + break; // entry found + } + if (!OpenVirtualDir(&vdir, vfile)) + vdir.virtual_src = 0; } - // failed if arriving here - return false; + return (name == NULL); // if name is NULL, this succeeded } +bool GetVirtualDir(VirtualDir* vdir, const char* path) { + VirtualFile vfile; + return GetVirtualFile(&vfile, path) && OpenVirtualDir(vdir, &vfile); +} + +// hacky solution, actually ignores path bool FindVirtualFileBySize(VirtualFile* vfile, const char* path, u32 size) { // get virtual source u32 virtual_src = 0; virtual_src = GetVirtualSource(path); if (!virtual_src) return false; - // read virtual dir, match the path / size - ReadVirtualDir(NULL, virtual_src); // reset dir reader - while (ReadVirtualDir(vfile, virtual_src)) { + VirtualDir vdir; // read virtual root dir, match size + OpenVirtualRoot(&vdir, virtual_src); // get dir reader object + while (ReadVirtualDir(vfile, &vdir)) { vfile->flags |= virtual_src; // add source flag if (vfile->size == size) // search by size should be a last resort solution return true; // file found @@ -88,17 +127,18 @@ bool FindVirtualFileBySize(VirtualFile* vfile, const char* path, u32 size) { bool GetVirtualDirContents(DirStruct* contents, const char* path, const char* pattern) { u32 virtual_src = GetVirtualSource(path); if (!virtual_src) return false; // not a virtual path - if (strchr(path, '/')) return false; // only top level paths + VirtualDir vdir; VirtualFile vfile; - ReadVirtualDir(NULL, virtual_src); // reset dir reader - while ((contents->n_entries < MAX_DIR_ENTRIES) && (ReadVirtualDir(&vfile, virtual_src))) { + if (!GetVirtualDir(&vdir, path)) + return false; // get dir reader object + while ((contents->n_entries < MAX_DIR_ENTRIES) && (ReadVirtualDir(&vfile, &vdir))) { DirEntry* entry = &(contents->entry[contents->n_entries]); if (pattern && !MatchName(pattern, vfile.name)) continue; snprintf(entry->path, 256, "%s/%s", path, vfile.name); entry->name = entry->path + strnlen(path, 256) + 1; entry->size = vfile.size; - entry->type = T_FILE; + entry->type = (vfile.flags & VFLAG_DIR) ? T_DIR : T_FILE; entry->marked = 0; contents->n_entries++; } diff --git a/source/virtual/virtual.h b/source/virtual/virtual.h index 763bab0..5d12412 100644 --- a/source/virtual/virtual.h +++ b/source/virtual/virtual.h @@ -10,7 +10,11 @@ #define VRT_MEMORY (1<<10) #define VRT_GAME (1<<11) +#define VRT_SOURCE (VRT_SYSNAND|VRT_EMUNAND|VRT_IMGNAND|VRT_MEMORY|VRT_GAME) + #define VFLAG_A9LH_AREA (1<<20) +#define VFLAG_DIR (1<<21) +#define VFLAG_ROOT (1<<22) // virtual file flag (subject to change): // bits 0...9 : reserved for NAND virtual sources and info @@ -19,12 +23,21 @@ // 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) - u32 size; + u64 offset; // must be a multiple of 0x200 (for NAND access) + u64 size; u32 keyslot; u32 flags; } __attribute__((packed)) VirtualFile; +// virtual dirs are only relevant for virtual game drives +typedef struct { + int index; + u64 offset; + u64 size; + u32 flags; + u32 virtual_src; +} __attribute__((packed)) VirtualDir; + u32 GetVirtualSource(const char* path); bool CheckVirtualDrive(const char* path); bool GetVirtualFile(VirtualFile* vfile, const char* path); diff --git a/source/virtual/vmem.c b/source/virtual/vmem.c index fa9253c..3e49eeb 100644 --- a/source/virtual/vmem.c +++ b/source/virtual/vmem.c @@ -19,19 +19,13 @@ static const VirtualFile vMemFileTemplates[] = { { "bootrom_unp.mem" , 0xFFFF0000, 0x00008000, 0xFF, 0 } }; -bool ReadVMemDir(VirtualFile* vfile) { - static int num = -1; +bool ReadVMemDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir object generated in virtual.c int n_templates = sizeof(vMemFileTemplates) / sizeof(VirtualFile); const VirtualFile* templates = vMemFileTemplates; - if (!vfile) { // NULL pointer -> reset dir reader / internal number - num = -1; - return true; - } - - while (++num < n_templates) { + while (++vdir->index < n_templates) { // copy current template to vfile - memcpy(vfile, templates + num, sizeof(VirtualFile)); + memcpy(vfile, templates + vdir->index, sizeof(VirtualFile)); // process special flag if ((vfile->flags & VFLAG_N3DS_ONLY) && (GetUnitPlatform() != PLATFORM_N3DS)) diff --git a/source/virtual/vmem.h b/source/virtual/vmem.h index 3c0cff3..706669e 100644 --- a/source/virtual/vmem.h +++ b/source/virtual/vmem.h @@ -3,6 +3,6 @@ #include "common.h" #include "virtual.h" -bool ReadVMemDir(VirtualFile* vfile); +bool ReadVMemDir(VirtualFile* vfile, VirtualDir* vdir); int ReadVMemFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count); int WriteVMemFile(const VirtualFile* vfile, const u8* buffer, u32 offset, u32 count); diff --git a/source/virtual/vnand.c b/source/virtual/vnand.c index 1ed3473..ffee3e6 100644 --- a/source/virtual/vnand.c +++ b/source/virtual/vnand.c @@ -34,23 +34,18 @@ bool CheckVNandDrive(u32 nand_src) { return GetNandSizeSectors(nand_src); } -bool ReadVNandDir(VirtualFile* vfile, u32 nand_src) { - static int num = -1; +bool ReadVNandDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir object generated in virtual.c int n_templates = sizeof(vNandFileTemplates) / sizeof(VirtualFile); const VirtualFile* templates = vNandFileTemplates; + u32 nand_src = vdir->virtual_src; - if (!vfile) { // NULL pointer -> reset dir reader / internal number - num = -1; - return true; - } - - while (++num < n_templates) { + while (++vdir->index < n_templates) { // get NAND type (O3DS/N3DS/NO3DS), workaround for empty EmuNAND u32 nand_type = CheckNandType(nand_src); if (!nand_type) nand_type = (GetUnitPlatform() == PLATFORM_3DS) ? NAND_TYPE_O3DS : NAND_TYPE_N3DS; // copy current template to vfile - memcpy(vfile, templates + num, sizeof(VirtualFile)); + memcpy(vfile, templates + vdir->index, sizeof(VirtualFile)); // process / check special flags if (!(vfile->flags & nand_type)) diff --git a/source/virtual/vnand.h b/source/virtual/vnand.h index 0c2b40a..02ca6dd 100644 --- a/source/virtual/vnand.h +++ b/source/virtual/vnand.h @@ -4,6 +4,6 @@ #include "virtual.h" bool CheckVNandDrive(u32 nand_src); -bool ReadVNandDir(VirtualFile* vfile, u32 nand_src); +bool ReadVNandDir(VirtualFile* vfile, VirtualDir* vdir); int ReadVNandFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count); int WriteVNandFile(const VirtualFile* vfile, const u8* buffer, u32 offset, u32 count);