diff --git a/source/common.h b/source/common.h index d4a0564..31ac35f 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.8.2" +#define VERSION "0.8.3" // input / output paths #define INPUT_PATHS "0:", "0:/files9", "0:/Decrypt9" diff --git a/source/game/ncch.c b/source/game/ncch.c index f550813..c4b520f 100644 --- a/source/game/ncch.c +++ b/source/game/ncch.c @@ -1,4 +1,23 @@ #include "ncch.h" +#include "keydb.h" +#include "aes.h" +#include "sha.h" +#include "ff.h" + +#define SEEDDB_NAME "seeddb.bin" +#define EXEFS_KEYID(name) (((strncmp(name, "banner", 8) == 0) || (strncmp(name, "icon", 8) == 0)) ? 0 : 1) + +typedef struct { + u64 titleId; + u8 seed[16]; + u8 reserved[8]; +} __attribute__((packed)) SeedInfoEntry; + +typedef struct { + u32 n_entries; + u8 padding[12]; + SeedInfoEntry entries[256]; // this number is only a placeholder +} __attribute__((packed)) SeedInfo; u32 ValidateNcchHeader(NcchHeader* header) { if (memcmp(header->magic, "NCCH", 4) != 0) // check magic number @@ -22,3 +41,230 @@ u32 ValidateNcchHeader(NcchHeader* header) { return 0; } + +u32 GetNcchCtr(u8* ctr, NcchHeader* ncch, u8 section) { + memset(ctr, 0x00, 16); + if (ncch->version == 1) { + memcpy(ctr, &(ncch->programId), 8); + if (section == 1) { // ExtHeader ctr + add_ctr(ctr, NCCH_EXTHDR_OFFSET); + } else if (section == 2) { // ExeFS ctr + add_ctr(ctr, ncch->offset_exefs * NCCH_MEDIA_UNIT); + } else if (section == 3) { // RomFS ctr + add_ctr(ctr, ncch->offset_romfs * NCCH_MEDIA_UNIT); + } + } else { + for (u32 i = 0; i < 8; i++) + ctr[i] = ((u8*) &(ncch->programId))[7-i]; + ctr[8] = section; + } + + return 0; +} + +u32 GetNcchSeed(u8* seed, NcchHeader* ncch) { + static u8 lseed[16+8] = { 0 }; // seed plus title ID for easy validation + u64 titleId = ncch->programId; + u32 hash_seed = ncch->hash_seed; + + UINT btr = 0; + FIL file; + char path[128]; + u32 sha256sum[8]; + + memcpy(lseed+16, &(ncch->programId), 8); + sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE); + if (hash_seed == sha256sum[0]) { + memcpy(seed, lseed, 16); + return 0; + } + + // try to grab the seed from NAND database + const char* nand_drv[] = {"1:", "4:"}; // SysNAND and EmuNAND + for (u32 i = 0; i < (sizeof(nand_drv)/sizeof(char*)); i++) { + // grab the key Y from movable.sed + u8 movable_keyy[16]; + snprintf(path, 128, "%s/private/movable.sed", nand_drv[i]); + if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) + continue; + f_lseek(&file, 0x110); + f_read(&file, movable_keyy, 0x10, &btr); + f_close(&file); + + // build the seed save path + sha_quick(sha256sum, movable_keyy, 0x10, SHA256_MODE); + snprintf(path, 128, "%s/data/%08lX%08lX%08lX%08lX/sysdata/0001000F/00000000", + nand_drv[i], sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]); + + // check seedsave for seed + u8* seedsave = (u8*) GAME_BUFFER; + if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) + continue; + f_read(&file, seedsave, 0x200, &btr); + u32 p_active = (getle32(seedsave + 0x168)) ? 1 : 0; + static const u32 seed_offset[2] = {0x7000, 0x5C000}; + for (u32 p = 0; p < 2; p++) { + f_lseek(&file, seed_offset[(p + p_active) % 2]); + f_read(&file, seedsave, 2000*(8+16), &btr); + for (u32 s = 0; s < 2000; s++) { + if (titleId != getle64(seedsave + (s*8))) + continue; + memcpy(lseed, seedsave + (2000*8) + (s*16), 16); + sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE); + if (hash_seed == sha256sum[0]) { + memcpy(seed, lseed, 16); + f_close(&file); + return 0; // found! + } + } + } + f_close(&file); + } + + // not found -> try seeddb.bin + const char* base[] = { INPUT_PATHS }; + for (u32 i = 0; i < (sizeof(base)/sizeof(char*)); i++) { + SeedInfo* seeddb = (SeedInfo*) GAME_BUFFER; + snprintf(path, 128, "%s/%s", base[i], SEEDDB_NAME); + if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) + continue; + f_read(&file, seeddb, GAME_BUFFER_SIZE, &btr); + f_close(&file); + if (seeddb->n_entries > (btr - 16) / 32) + continue; // filesize / seeddb size mismatch + for (u32 s = 0; s < seeddb->n_entries; s++) { + if (titleId != seeddb->entries[s].titleId) + continue; + memcpy(lseed, seeddb->entries[s].seed, 16); + sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE); + if (hash_seed == sha256sum[0]) { + memcpy(seed, lseed, 16); + return 0; // found! + } + } + } + + // out of options -> failed! + return 1; +} + +u32 SetNcchKey(NcchHeader* ncch, u32 keyid) { + u32 keyslot = (!keyid || !ncch->flags[3]) ? 0x2C : // standard / secure3 / secure4 / 7.x crypto + (ncch->flags[3] == 0x0A) ? 0x18 : (ncch->flags[3] == 0x0B) ? 0x1B : 0x25; + + if (!NCCH_ENCRYPTED(ncch)) + return 1; + + if (ncch->flags[7] & 0x01) { // fixed key crypto + // from https://github.com/profi200/Project_CTR/blob/master/makerom/pki/dev.h + u8 zeroKey[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // zero key + u8 sysKey[16] = { 0x52, 0x7C, 0xE6, 0x30, 0xA9, 0xCA, 0x30, 0x5F, + 0x36, 0x96, 0xF3, 0xCD, 0xE9, 0x54, 0x19, 0x4B }; // fixed sys key + setup_aeskey(0x11, (ncch->programId & ((u64) 0x10 << 32)) ? sysKey : zeroKey); + use_aeskey(0x11); + return 0; + } + + // load key X from file if required + if ((keyslot != 0x2C) && (LoadKeyFromFile(NULL, keyslot, 'X', NULL) != 0)) + return 1; + + // key Y for seed and non seed + if (keyid && (ncch->flags[7] & 0x20)) { // seed crypto + u8 keydata[16+16]; + u8 seedkeyY[16+16]; + memcpy(keydata, ncch->signature, 16); + if (GetNcchSeed(keydata + 16, ncch) != 0) + return 1; + sha_quick(seedkeyY, keydata, 32, SHA256_MODE); + setup_aeskeyY(keyslot, seedkeyY); + } else { // no seed crypto + setup_aeskeyY(keyslot, ncch->signature); + } + use_aeskey(keyslot); + + return 0; +} + +u32 CheckNcchCrypto(NcchHeader* ncch) { + return (!NCCH_ENCRYPTED(ncch)) ? 1 : + ((SetNcchKey(ncch, 0) == 0) && (SetNcchKey(ncch, 1) == 0)) ? 0 : 1; +} + +u32 DecryptNcchSection(u8* data, u32 offset_data, u32 size_data, + u32 offset_section, u32 size_section, u32 offset_ctr, NcchHeader* ncch, u32 snum, u32 keyid) { + const u32 mode = AES_CNT_CTRNAND_MODE; + + // check if section in data + if ((offset_section >= offset_data + size_data) || + (offset_data >= offset_section + size_section) || + !size_section) { + return 0; // section not in data + } + + // determine data / offset / size + u8* data_i = data; + u32 offset_i = 0; + u32 size_i = size_section; + if (offset_section < offset_data) + offset_i = offset_data - offset_section; + else data_i = data + (offset_section - offset_data); + size_i = size_section - offset_i; + if (size_i > size_data - (data_i - data)) + size_i = size_data - (data_i - data); + + // actual decryption stuff + u8 ctr[16]; + GetNcchCtr(ctr, ncch, snum); + if (SetNcchKey(ncch, keyid) != 0) return 1; + ctr_decrypt_boffset(data_i, data_i, size_i, offset_i + offset_ctr, mode, ctr); + + return 0; +} + +// on the fly decryptor for NCCH +u32 DecryptNcch(u8* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs) { + const u32 offset_flag3 = 0x188 + 3; + const u32 offset_flag7 = 0x188 + 7; + + // check for encryption + if (!NCCH_ENCRYPTED(ncch)) + return 0; + + // ncch flags handling + if ((offset <= offset_flag3) && (offset + size > offset_flag3)) + data[offset_flag3 - offset] = 0; + if ((offset <= offset_flag7) && (offset + size > offset_flag7)) { + data[offset_flag7 - offset] &= ~(0x01|0x20); + data[offset_flag7 - offset] |= 0x04; + } + + // exthdr handling + if (DecryptNcchSection(data, offset, size, + NCCH_EXTHDR_OFFSET, + NCCH_EXTHDR_SIZE, + 0, ncch, 1, 0) != 0) return 1; + + // exefs header handling + if (DecryptNcchSection(data, offset, size, + ncch->offset_exefs * NCCH_MEDIA_UNIT, + 0x200, 0, ncch, 2, 0) != 0) return 1; + + // exefs file handling + if (exefs) for (u32 i = 0; i < 10; i++) { + ExeFsFileHeader* file = exefs->files + i; + if (DecryptNcchSection(data, offset, size, + (ncch->offset_exefs * NCCH_MEDIA_UNIT) + 0x200 + file->offset, + file->size, 0x200 + file->offset, + ncch, 2, EXEFS_KEYID(file->name)) != 0) return 1; + } + + // romfs handling + if (DecryptNcchSection(data, offset, size, + ncch->offset_romfs * NCCH_MEDIA_UNIT, + ncch->size_romfs * NCCH_MEDIA_UNIT, + 0, ncch, 3, 1) != 0) return 1; + + return 0; +} diff --git a/source/game/ncch.h b/source/game/ncch.h index 71ed337..5ae3e78 100644 --- a/source/game/ncch.h +++ b/source/game/ncch.h @@ -1,6 +1,7 @@ #pragma once #include "common.h" +#include "exefs.h" #define NCCH_MEDIA_UNIT 0x200 @@ -33,7 +34,7 @@ typedef struct { u64 partitionId; u16 makercode; u16 version; - u8 hash_seed[0x4]; + u32 hash_seed; u64 programId; u8 reserved0[0x10]; u8 hash_logo[0x20]; @@ -59,3 +60,5 @@ typedef struct { } __attribute__((packed)) NcchHeader; u32 ValidateNcchHeader(NcchHeader* header); +u32 CheckNcchCrypto(NcchHeader* ncch); +u32 DecryptNcch(u8* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs); diff --git a/source/virtual/vgame.c b/source/virtual/vgame.c index 4a4850b..0372d3f 100644 --- a/source/virtual/vgame.c +++ b/source/virtual/vgame.c @@ -4,6 +4,7 @@ #include "aes.h" #include "ff.h" +#define VFLAG_EXEFS_FILE (1<<25) #define VFLAG_EXTHDR (1<<26) #define VFLAG_CIA (1<<27) #define VFLAG_NCSD (1<<28) @@ -20,18 +21,15 @@ #define NAME_CIA_TMD "tmd.bin" #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_CIA_CONTENT "%04X.%08lX%s" // index.id(.ext) #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" -#define NAME_NCSD_DIR "cnt0.game", "cnt1.manual", "cnt2.dlp", \ - "cnt3", "cnt4", "cnt5", \ - "cnt6.update_n3ds", "cnt7.update_o3ds" +#define NAME_NCSD_TYPES "game", "manual", "dlp", \ + "unk", "unk", "unk", \ + "update_n3ds", "update_o3ds" +#define NAME_NCSD_CONTENT "content%lu.%s%s" // content?.type(.ext) #define NAME_NCCH_HEADER "ncch.bin" #define NAME_NCCH_EXTHEADER "extheader.bin" @@ -80,8 +78,8 @@ bool BuildVGameExeFsDir(void) { snprintf(templates[n].name, 32, "%.8s", file->name); templates[n].offset = offset_exefs + sizeof(ExeFsHeader) + file->offset; templates[n].size = file->size; - templates[n].keyslot = 0xFF; // needs to be handled - templates[n].flags = 0; + templates[n].keyslot = NCCH_ENCRYPTED(ncch) ? 0x2C : 0xFF; // actual keyslot may be different + templates[n].flags = VFLAG_EXEFS_FILE; n++; } @@ -93,6 +91,9 @@ bool BuildVGameNcchDir(void) { VirtualFile* templates = templates_ncch; u32 n = 0; + // NCCH crypto + bool ncch_crypto = (CheckNcchCrypto(ncch) == 0); + // header strncpy(templates[n].name, NAME_NCCH_HEADER, 32); templates[n].offset = offset_ncch + 0; @@ -106,7 +107,7 @@ bool BuildVGameNcchDir(void) { 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].keyslot = ncch_crypto ? 0x2C : 0xFF; templates[n].flags = VFLAG_EXTHDR; n++; } @@ -136,10 +137,10 @@ bool BuildVGameNcchDir(void) { 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; // crypto ? + templates[n].keyslot = ncch_crypto ? 0x2C : 0xFF; // real slot may be something else templates[n].flags = VFLAG_EXEFS; n++; - if (!NCCH_ENCRYPTED(ncch)) { + if (!NCCH_ENCRYPTED(ncch) || ncch_crypto) { memcpy(templates + n, templates + n - 1, sizeof(VirtualFile)); strncpy(templates[n].name, NAME_NCCH_EXEFSDIR, 32); templates[n].flags |= VFLAG_DIR; @@ -152,10 +153,10 @@ bool BuildVGameNcchDir(void) { 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; // crypto ? + templates[n].keyslot = ncch_crypto ? 0x2C : 0xFF; // real slot may be something else templates[n].flags = VFLAG_ROMFS; n++; - if (!NCCH_ENCRYPTED(ncch)) { + if (!NCCH_ENCRYPTED(ncch) || ncch_crypto) { memcpy(templates + n, templates + n - 1, sizeof(VirtualFile)); strncpy(templates[n].name, NAME_NCCH_ROMFSDIR, 32); templates[n].flags |= VFLAG_DIR; @@ -168,8 +169,7 @@ bool BuildVGameNcchDir(void) { } bool BuildVGameNcsdDir(void) { - const char* name_content[] = { NAME_NCSD_CONTENT }; - const char* name_dir[] = { NAME_NCSD_DIR }; + const char* name_type[] = { NAME_NCSD_TYPES }; VirtualFile* templates = templates_ncsd; u32 n = 0; @@ -206,14 +206,14 @@ bool BuildVGameNcsdDir(void) { NcchPartition* partition = ncsd->partitions + i; if ((partition->offset == 0) && (partition->size == 0)) continue; - strncpy(templates[n].name, name_content[i], 32); + snprintf(templates[n].name, 32, NAME_NCSD_CONTENT, i, name_type[i], ".app"); 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); + snprintf(templates[n].name, 32, NAME_NCSD_CONTENT, i, name_type[i], ""); templates[n].flags |= VFLAG_DIR; n++; } @@ -302,7 +302,7 @@ bool BuildVGameCiaDir(void) { is_ncch = (ValidateNcchHeader(&ncch) == 0); } snprintf(templates[n].name, 32, NAME_CIA_CONTENT, - getbe16(content_list[i].index), getbe32(content_list[i].id)); + 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 @@ -310,8 +310,8 @@ bool BuildVGameCiaDir(void) { 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)); + snprintf(templates[n].name, 32, NAME_CIA_CONTENT, + getbe16(content_list[i].index), getbe32(content_list[i].id), ""); templates[n].flags |= VFLAG_DIR; n++; } @@ -347,6 +347,14 @@ u32 CheckVGameDrive(void) { return vgame_type; } +int ReadNcchImageBytes(u8* buffer, u64 offset, u64 count) { + int ret = ReadImageBytes(buffer, offset, count); + if ((offset_ncch == (u64) -1) || (ret != 0)) return ret; + if (NCCH_ENCRYPTED(ncch) && (DecryptNcch(buffer, offset - offset_ncch, count, ncch, + (offset_exefs == (u64) -1) ? NULL : exefs) != 0)) return -1; + return 0; +} + bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) { // build vdir object vdir->index = -1; @@ -385,8 +393,14 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) { return false; offset_ncch = vdir->offset; if (!BuildVGameNcchDir()) return false; + u32 ncch_offset_exefs = offset_ncch + (ncch->offset_exefs * NCCH_MEDIA_UNIT); + if ((ReadNcchImageBytes((u8*) exefs, ncch_offset_exefs, sizeof(ExeFsHeader)) != 0) || + (ValidateExeFsHeader(exefs, ncch->size_exefs * NCCH_MEDIA_UNIT) != 0)) + return false; + offset_exefs = ncch_offset_exefs; + if (!BuildVGameExeFsDir()) return false; } else if ((vdir->flags & VFLAG_EXEFS) && (offset_exefs != vdir->offset)) { - if ((ReadImageBytes((u8*) exefs, vdir->offset, sizeof(ExeFsHeader)) != 0) || + if ((ReadNcchImageBytes((u8*) exefs, vdir->offset, sizeof(ExeFsHeader)) != 0) || (ValidateExeFsHeader(exefs, ncch->size_exefs * NCCH_MEDIA_UNIT) != 0)) return false; offset_exefs = vdir->offset; @@ -395,20 +409,20 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) { // validate romFS magic u8 magic[] = { ROMFS_MAGIC }; u8 header[sizeof(magic)]; - if ((ReadImageBytes(header, vdir->offset, sizeof(magic)) != 0) || + if ((ReadNcchImageBytes(header, vdir->offset, sizeof(magic)) != 0) || (memcmp(magic, header, sizeof(magic)) != 0)) return false; // validate lv3 header RomFsLv3Header* lv3 = (RomFsLv3Header*) romfslv3; for (u32 i = 1; i < 8; i++) { offset_lv3 = vdir->offset + (i*OFFSET_LV3); - if (ReadImageBytes(romfslv3, offset_lv3, sizeof(RomFsLv3Header)) != 0) + if (ReadNcchImageBytes(romfslv3, offset_lv3, sizeof(RomFsLv3Header)) != 0) return false; if (ValidateLv3Header(lv3, VGAME_BUFFER_SIZE - 0x20000) == 0) break; offset_lv3 = (u64) -1; } - if ((offset_lv3 == (u64) -1) || (ReadImageBytes(romfslv3, offset_lv3, lv3->offset_filedata) != 0)) + if ((offset_lv3 == (u64) -1) || (ReadNcchImageBytes(romfslv3, offset_lv3, lv3->offset_filedata) != 0)) return false; offset_lv3fd = offset_lv3 + lv3->offset_filedata; offset_romfs = vdir->offset; @@ -430,6 +444,7 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) { bool ReadVGameDirLv3(VirtualFile* vfile, VirtualDir* vdir) { BuildLv3Index(&lv3idx, romfslv3); vfile->flags = VFLAG_LV3; + vfile->keyslot = NCCH_ENCRYPTED(ncch) ? 0x2C : 0xFF; // actual keyslot may be different // start from parent dir object if (vdir->index == -1) vdir->index = 0; @@ -534,32 +549,16 @@ int ReadVGameFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count) { lv3file = LV3_GET_FILE(vfile->offset, &lv3idx); vfoffset = offset_lv3fd + lv3file->offset_data; } - int ret = ReadImageBytes(buffer, vfoffset + offset, count); - if (ret != 0) return ret; - /*if ((ret != 0) && (vfile->keyslot <= 0x40)) { // crypto - // relies on first template being the header and everything aligned to AES_BLOCK_SIZE - u32 offset_base = 0; // vfoffset - (*templates).offset; - u8 ctr[16] = { 0 }; - ctr[0] = (vfile->index & 0xFF); - ctr[1] = (vfile->index >> 8); - setup_aeskeyY(0x11, titlekey); - use_aeskey(0x11); - ctr_decrypt_boffset(buffer, buffer, bytes_read, offset - offset_base, - AES_CNT_TITLEKEY_DECRYPT_MODE, ctr); - }*/ - return 0; + if (NCCH_ENCRYPTED(ncch) && (vfile->keyslot < 0x40) && + (vfile->flags & (VFLAG_EXEFS_FILE|VFLAG_EXTHDR|VFLAG_EXEFS|VFLAG_ROMFS|VFLAG_LV3|VFLAG_NCCH))) + return ReadNcchImageBytes(buffer, vfoffset + offset, count); + else return ReadImageBytes(buffer, vfoffset + offset, count); } bool FindVirtualFileInLv3Dir(VirtualFile* vfile, const VirtualDir* vdir, const char* name) { vfile->name[0] = '\0'; vfile->flags = vdir->flags & ~VFLAG_DIR; - - RomFsLv3FileMeta* lv3file = GetLv3FileMeta(name, vdir->offset, &lv3idx); - if (lv3file) { - vfile->offset = ((u8*) lv3file) - ((u8*) lv3idx.filemeta); - vfile->size = lv3file->size_data; - return true; - } + vfile->keyslot = NCCH_ENCRYPTED(ncch) ? 0x2C : 0xFF; // actual keyslot may be different RomFsLv3DirMeta* lv3dir = GetLv3DirMeta(name, vdir->offset, &lv3idx); if (lv3dir) { @@ -569,6 +568,13 @@ bool FindVirtualFileInLv3Dir(VirtualFile* vfile, const VirtualDir* vdir, const c return true; } + RomFsLv3FileMeta* lv3file = GetLv3FileMeta(name, vdir->offset, &lv3idx); + if (lv3file) { + vfile->offset = ((u8*) lv3file) - ((u8*) lv3idx.filemeta); + vfile->size = lv3file->size_data; + return true; + } + return false; } diff --git a/source/virtual/vgame.h b/source/virtual/vgame.h index 10509db..bc6284f 100644 --- a/source/virtual/vgame.h +++ b/source/virtual/vgame.h @@ -12,5 +12,6 @@ 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 +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);