From 15431ec2c8765f078a0b9b897a4ab216f243e76c Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Wed, 21 Oct 2020 00:27:48 -0400 Subject: [PATCH] Many changes. * AES: moved CTR initializing/updating functions here from nca.c. * BKTR/RomFS/PFS: check if we're dealing with a NCA with titlekey crypto and the titlekey hasn't been retrieved. * BFTTF: use void pointers for output font data. * Mem: Only exclude Unmapped/Uo/ThreadLocal/Reserved memory pages if dealing with FS. * NCA: use content type context pointers inside NCA contexts to manage ContentMeta, ProgramInfo, Nacp and LegalInfo contexts. * NCA: added 'written' bool elements to patch structs to indicate patch write completion. * NPDM: remove unnecessary inline functions, generate PFS patch right after changing ACID data, add a pfsWriteEntryPatchToMemoryBuffer wrapper. * PFS: added PartitionFileSystemFileContext and related functions to deal with NSP headers. * ProgramInfo: removed unnecessary inline functions. * Save: added commented code to dump a full system savefile - will probably use it down the road. * Tik: added support for volatile tickets (thanks to @shchmue and @Whovian9369!), added a rights ID string representation to the Ticket struct, clear Volatile and ELicenseRequired flags in conversions to common tickets. * Title: added a function to calculate the number of titles (current + siblings) from a TItleInfo block. * Utils: added a function to generate a dynamically allocated path string using a prefix, a filename and a extension. * Removed explicit offset checks throughout all the code. * Codestyle fixes. * Updated to-do. --- .gitignore | 1 + build.sh | 7 +- code_templates/dump_title_infos.c | 2 +- source/aes.h | 52 ++++++++ source/bfttf.h | 2 +- source/bktr.c | 10 +- source/bktr.h | 38 +++--- source/cnmt.c | 10 +- source/gamecard.c | 16 ++- source/keys.c | 12 +- source/legal_info.c | 4 + source/mem.c | 5 +- source/mem.h | 5 +- source/nacp.c | 10 +- source/nca.c | 142 ++++++++------------ source/nca.h | 27 ++-- source/npdm.c | 32 ++++- source/npdm.h | 19 +-- source/pfs.c | 165 +++++++++++++++++++++-- source/pfs.h | 34 +++++ source/program_info.c | 4 + source/program_info.h | 9 +- source/romfs.c | 14 +- source/save.c | 26 ++++ source/tik.c | 211 ++++++++++++++++++++++-------- source/tik.h | 5 +- source/title.c | 4 +- source/title.h | 28 +++- source/usb.c | 6 +- source/utils.c | 22 ++++ source/utils.h | 2 + todo.txt | 28 ++-- 32 files changed, 687 insertions(+), 265 deletions(-) diff --git a/.gitignore b/.gitignore index 5388b81..1dd94d6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ build /*.pfs0 /*.lst /*.tar.bz2 +/code_templates/nsp_dumper.c /code_templates/tmp/* /source/main.c /*.log diff --git a/build.sh b/build.sh index adff5e4..0fe878e 100644 --- a/build.sh +++ b/build.sh @@ -21,13 +21,12 @@ for f in ./code_templates/*.c; do rm -f ./source/main.c cp $f ./source/main.c - { - make clean - make -j 12 BUILD_TYPE="$filename" - } + make clean &> /dev/null + make -j 12 BUILD_TYPE="$filename" mkdir ./code_templates/tmp/$filename cp ./nxdumptool-rewrite.nro ./code_templates/tmp/$filename/nxdumptool-rewrite.nro + #cp ./nxdumptool-rewrite.elf ./code_templates/tmp/$filename/nxdumptool-rewrite.elf done cd ./code_templates/tmp diff --git a/code_templates/dump_title_infos.c b/code_templates/dump_title_infos.c index 9e968f2..948df0d 100644 --- a/code_templates/dump_title_infos.c +++ b/code_templates/dump_title_infos.c @@ -17,7 +17,7 @@ g_titleInfo[i].version.minor_relstep); fprintf(title_infos_txt, "Type: 0x%02X\r\n", g_titleInfo[i].meta_key.type); fprintf(title_infos_txt, "Install Type: 0x%02X\r\n", g_titleInfo[i].meta_key.install_type); - fprintf(title_infos_txt, "Title Size: %s (0x%lX)\r\n", g_titleInfo[i].title_size_str, g_titleInfo[i].title_size); + fprintf(title_infos_txt, "Title Size: %s (0x%lX)\r\n", g_titleInfo[i].size_str, g_titleInfo[i].size); fprintf(title_infos_txt, "Content Count: %u\r\n", g_titleInfo[i].content_count); for(u32 j = 0; j < g_titleInfo[i].content_count; j++) diff --git a/source/aes.h b/source/aes.h index b220634..91234d4 100644 --- a/source/aes.h +++ b/source/aes.h @@ -28,4 +28,56 @@ /// 'dst' and 'src' can both point to the same address. size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, size_t sector_size, bool encrypt); +/// Initializes an output AES partial counter using an initial CTR value and an offset. +/// The size for both 'out' and 'ctr' should be at least AES_BLOCK_SIZE. +NX_INLINE void aes128CtrInitializePartialCtr(u8 *out, const u8 *ctr, u64 offset) +{ + if (!out || !ctr) return; + + offset >>= 4; + + for(u8 i = 0; i < 8; i++) + { + out[i] = ctr[0x8 - i - 1]; + out[0x10 - i - 1] = (u8)(offset & 0xFF); + offset >>= 8; + } +} + +/// Updates the provided AES partial counter using an offset. +/// 'out' size should be at least AES_BLOCK_SIZE. +NX_INLINE void aes128CtrUpdatePartialCtr(u8 *ctr, u64 offset) +{ + if (!ctr) return; + + offset >>= 4; + + for(u8 i = 0; i < 8; i++) + { + ctr[0x10 - i - 1] = (u8)(offset & 0xFF); + offset >>= 8; + } +} + +/// Updates the provided AES partial counter using an offset and a 32-bit CTR value. +/// 'out' size should be at least AES_BLOCK_SIZE. +NX_INLINE void aes128CtrUpdatePartialCtrEx(u8 *ctr, u32 ctr_val, u64 offset) +{ + if (!ctr) return; + + offset >>= 4; + + for(u8 i = 0; i < 8; i++) + { + ctr[0x10 - i - 1] = (u8)(offset & 0xFF); + offset >>= 8; + } + + for(u8 i = 0; i < 4; i++) + { + ctr[0x8 - i - 1] = (u8)(ctr_val & 0xFF); + ctr_val >>= 8; + } +} + #endif /* __AES_H__ */ diff --git a/source/bfttf.h b/source/bfttf.h index d422f63..89025b5 100644 --- a/source/bfttf.h +++ b/source/bfttf.h @@ -40,7 +40,7 @@ typedef enum { typedef struct { u8 type; ///< BfttfFontType. u32 size; ///< Decoded BFTFF font size. - u8 *ptr; ///< Pointer to font data. + void *ptr; ///< Pointer to font data. } BfttfFontData; /// Initializes the BFTTF interface. diff --git a/source/bktr.c b/source/bktr.c index 9d64a06..64a7608 100644 --- a/source/bktr.c +++ b/source/bktr.c @@ -44,7 +44,8 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct __builtin_bswap32(update_nca_fs_ctx->header.patch_info.indirect_bucket.header.magic) != NCA_BKTR_MAGIC || \ __builtin_bswap32(update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.header.magic) != NCA_BKTR_MAGIC || \ (update_nca_fs_ctx->header.patch_info.indirect_bucket.offset + update_nca_fs_ctx->header.patch_info.indirect_bucket.size) != update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset || \ - (update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset + update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.size) != update_nca_fs_ctx->section_size) + (update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset + update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.size) != update_nca_fs_ctx->section_size || \ + (base_nca_ctx->rights_id_available && !base_nca_ctx->titlekey_retrieved) || (update_nca_ctx->rights_id_available && !update_nca_ctx->titlekey_retrieved)) { LOGFILE("Invalid parameters!"); return false; @@ -218,7 +219,7 @@ end: bool bktrReadFileSystemData(BktrContext *ctx, void *out, u64 read_size, u64 offset) { - if (!ctx || !ctx->size || !out || !read_size || offset >= ctx->size || (offset + read_size) > ctx->size) + if (!ctx || !ctx->size || !out || !read_size || (offset + read_size) > ctx->size) { LOGFILE("Invalid parameters!"); return false; @@ -236,8 +237,7 @@ bool bktrReadFileSystemData(BktrContext *ctx, void *out, u64 read_size, u64 offs bool bktrReadFileEntryData(BktrContext *ctx, RomFileSystemFileEntry *file_entry, void *out, u64 read_size, u64 offset) { - if (!ctx || !ctx->body_offset || !file_entry || !file_entry->size || file_entry->offset >= ctx->size || (file_entry->offset + file_entry->size) > ctx->size || \ - !out || !read_size || offset >= file_entry->size || (offset + read_size) > file_entry->size) + if (!ctx || !ctx->body_offset || !file_entry || !file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !out || !read_size || (offset + read_size) > file_entry->size) { LOGFILE("Invalid parameters!"); return false; @@ -255,7 +255,7 @@ bool bktrReadFileEntryData(BktrContext *ctx, RomFileSystemFileEntry *file_entry, bool bktrIsFileEntryUpdated(BktrContext *ctx, RomFileSystemFileEntry *file_entry, bool *out) { - if (!ctx || !ctx->body_offset || !ctx->indirect_block || !file_entry || !file_entry->size || file_entry->offset >= ctx->size || (file_entry->offset + file_entry->size) > ctx->size || !out) + if (!ctx || !ctx->body_offset || !ctx->indirect_block || !file_entry || !file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !out) { LOGFILE("Invalid parameters!"); return false; diff --git a/source/bktr.h b/source/bktr.h index 30b2eab..512e272 100644 --- a/source/bktr.h +++ b/source/bktr.h @@ -90,17 +90,6 @@ typedef struct { /// Initializes a BKTR context. bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ctx, NcaFsSectionContext *update_nca_fs_ctx); -/// Cleanups a previously initialized BKTR context. -NX_INLINE void bktrFreeContext(BktrContext *ctx) -{ - if (!ctx) return; - romfsFreeContext(&(ctx->base_romfs_ctx)); - romfsFreeContext(&(ctx->patch_romfs_ctx)); - if (ctx->indirect_block) free(ctx->indirect_block); - if (ctx->aes_ctr_ex_block) free(ctx->aes_ctr_ex_block); - memset(ctx, 0, sizeof(BktrContext)); -} - /// Reads raw filesystem data using a BKTR context. /// Input offset must be relative to the start of the patched RomFS image. bool bktrReadFileSystemData(BktrContext *ctx, void *out, u64 read_size, u64 offset); @@ -113,46 +102,55 @@ bool bktrReadFileEntryData(BktrContext *ctx, RomFileSystemFileEntry *file_entry, bool bktrIsFileEntryUpdated(BktrContext *ctx, RomFileSystemFileEntry *file_entry, bool *out); /// Miscellaneous functions. -/// These are just wrappers for RomFS functions. + +NX_INLINE void bktrFreeContext(BktrContext *ctx) +{ + if (!ctx) return; + romfsFreeContext(&(ctx->base_romfs_ctx)); + romfsFreeContext(&(ctx->patch_romfs_ctx)); + if (ctx->indirect_block) free(ctx->indirect_block); + if (ctx->aes_ctr_ex_block) free(ctx->aes_ctr_ex_block); + memset(ctx, 0, sizeof(BktrContext)); +} NX_INLINE RomFileSystemDirectoryEntry *bktrGetDirectoryEntryByOffset(BktrContext *ctx, u32 dir_entry_offset) { - return (ctx != NULL ? romfsGetDirectoryEntryByOffset(&(ctx->patch_romfs_ctx), dir_entry_offset) : NULL); + return (ctx ? romfsGetDirectoryEntryByOffset(&(ctx->patch_romfs_ctx), dir_entry_offset) : NULL); } NX_INLINE RomFileSystemFileEntry *bktrGetFileEntryByOffset(BktrContext *ctx, u32 file_entry_offset) { - return (ctx != NULL ? romfsGetFileEntryByOffset(&(ctx->patch_romfs_ctx), file_entry_offset) : NULL); + return (ctx ? romfsGetFileEntryByOffset(&(ctx->patch_romfs_ctx), file_entry_offset) : NULL); } NX_INLINE bool bktrGetTotalDataSize(BktrContext *ctx, u64 *out_size) { - return (ctx != NULL ? romfsGetTotalDataSize(&(ctx->patch_romfs_ctx), out_size) : false); + return (ctx ? romfsGetTotalDataSize(&(ctx->patch_romfs_ctx), out_size) : false); } NX_INLINE bool bktrGetDirectoryDataSize(BktrContext *ctx, RomFileSystemDirectoryEntry *dir_entry, u64 *out_size) { - return (ctx != NULL ? romfsGetDirectoryDataSize(&(ctx->patch_romfs_ctx), dir_entry, out_size) : false); + return (ctx ? romfsGetDirectoryDataSize(&(ctx->patch_romfs_ctx), dir_entry, out_size) : false); } NX_INLINE RomFileSystemDirectoryEntry *bktrGetDirectoryEntryByPath(BktrContext *ctx, const char *path) { - return (ctx != NULL ? romfsGetDirectoryEntryByPath(&(ctx->patch_romfs_ctx), path) : NULL); + return (ctx ? romfsGetDirectoryEntryByPath(&(ctx->patch_romfs_ctx), path) : NULL); } NX_INLINE RomFileSystemFileEntry *bktrGetFileEntryByPath(BktrContext *ctx, const char *path) { - return (ctx != NULL ? romfsGetFileEntryByPath(&(ctx->patch_romfs_ctx), path) : NULL); + return (ctx ? romfsGetFileEntryByPath(&(ctx->patch_romfs_ctx), path) : NULL); } NX_INLINE bool bktrGeneratePathFromDirectoryEntry(BktrContext *ctx, RomFileSystemDirectoryEntry *dir_entry, char *out_path, size_t out_path_size, u8 illegal_char_replace_type) { - return (ctx != NULL ? romfsGeneratePathFromDirectoryEntry(&(ctx->patch_romfs_ctx), dir_entry, out_path, out_path_size, illegal_char_replace_type) : false); + return (ctx ? romfsGeneratePathFromDirectoryEntry(&(ctx->patch_romfs_ctx), dir_entry, out_path, out_path_size, illegal_char_replace_type) : false); } NX_INLINE bool bktrGeneratePathFromFileEntry(BktrContext *ctx, RomFileSystemFileEntry *file_entry, char *out_path, size_t out_path_size, u8 illegal_char_replace_type) { - return (ctx != NULL ? romfsGeneratePathFromFileEntry(&(ctx->patch_romfs_ctx), file_entry, out_path, out_path_size, illegal_char_replace_type) : false); + return (ctx ? romfsGeneratePathFromFileEntry(&(ctx->patch_romfs_ctx), file_entry, out_path, out_path_size, illegal_char_replace_type) : false); } #endif /* __BKTR_H__ */ diff --git a/source/cnmt.c b/source/cnmt.c index 0519c76..19afe92 100644 --- a/source/cnmt.c +++ b/source/cnmt.c @@ -124,9 +124,6 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx) /* Calculate SHA-256 checksum for the whole raw CNMT. */ sha256CalculateHash(out->raw_data_hash, out->raw_data, out->raw_data_size); - /* Save pointer to NCA context to the output CNMT context. */ - out->nca_ctx = nca_ctx; - /* Verify packaged header. */ out->packaged_header = (ContentMetaPackagedContentMetaHeader*)out->raw_data; cur_offset += sizeof(ContentMetaPackagedContentMetaHeader); @@ -235,6 +232,13 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx) goto end; } + /* Update output context. */ + out->nca_ctx = nca_ctx; + + /* Update content type context info in NCA context. */ + nca_ctx->content_type_ctx = out; + nca_ctx->content_type_ctx_patch = false; + success = true; end: diff --git a/source/gamecard.c b/source/gamecard.c index 6773a1b..6927f2b 100644 --- a/source/gamecard.c +++ b/source/gamecard.c @@ -102,7 +102,7 @@ static u8 g_gameCardStorageCurrentArea = GameCardStorageArea_None; static u8 *g_gameCardReadBuf = NULL; static GameCardHeader g_gameCardHeader = {0}; -static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0; +static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0, g_gameCardStorageTotalSize = 0; static u64 g_gameCardCapacity = 0; static u8 *g_gameCardHfsRootHeader = NULL; /// GameCardHashFileSystemHeader + (entry_count * GameCardHashFileSystemEntry) + Name Table. @@ -325,7 +325,7 @@ bool gamecardGetTotalSize(u64 *out) { mutexLock(&g_gamecardMutex); bool ret = (g_gameCardInserted && g_gameCardInfoLoaded && out); - if (ret) *out = (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize); + if (ret) *out = g_gameCardStorageTotalSize; mutexUnlock(&g_gamecardMutex); return ret; } @@ -744,8 +744,7 @@ static void gamecardFreeInfo(void) { memset(&g_gameCardHeader, 0, sizeof(GameCardHeader)); - g_gameCardStorageNormalAreaSize = 0; - g_gameCardStorageSecureAreaSize = 0; + g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = g_gameCardStorageTotalSize = 0; g_gameCardCapacity = 0; @@ -807,6 +806,8 @@ static bool gamecardReadInitialData(GameCardKeyArea *out) /* Look for the initial data block in the FS memory dump using the package ID and the initial data hash from the gamecard header. */ for(u64 offset = 0; offset < g_fsProgramMemory.data_size; offset++) { + if ((g_fsProgramMemory.data_size - offset) < sizeof(GameCardInitialData)) break; + if (memcmp(g_fsProgramMemory.data + offset, &(g_gameCardHeader.package_id), sizeof(g_gameCardHeader.package_id)) != 0) continue; sha256CalculateHash(tmp_hash, g_fsProgramMemory.data + offset, sizeof(GameCardInitialData)); @@ -910,8 +911,7 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool l bool success = false; - if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !read_size || \ - offset >= (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize) || (offset + read_size) > (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize)) + if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !read_size || (offset + read_size) > g_gameCardStorageTotalSize) { LOGFILE("Invalid parameters!"); goto end; @@ -1026,7 +1026,7 @@ static bool gamecardGetStorageAreasSizes(void) if (R_FAILED(rc) || !area_size) { LOGFILE("fsStorageGetSize failed to retrieve %s storage area size! (0x%08X).", GAMECARD_STORAGE_AREA_NAME(area), rc); - g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = 0; + g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = g_gameCardStorageTotalSize = 0; return false; } @@ -1038,6 +1038,8 @@ static bool gamecardGetStorageAreasSizes(void) } } + g_gameCardStorageTotalSize = (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize); + return true; } diff --git a/source/keys.c b/source/keys.c index 7f90d76..6245bd4 100644 --- a/source/keys.c +++ b/source/keys.c @@ -59,7 +59,7 @@ typedef struct { u8 eticket_rsa_kek_personalized[0x10]; ///< eTicket RSA kek (console-specific). u8 titlekeks[0x20][0x10]; ///< Titlekey encryption keys. - ///< Needed to reencrypt the NCA key area for tik-less NSP dumps. Retrieved from the Lockpick_RCM keys file. + ///< Needed to reencrypt the key area from NCAs with titlekey crypto removed. Retrieved from the Lockpick_RCM keys file. u8 key_area_keys[0x20][3][0x10]; ///< Key area encryption keys. } keysNcaKeyset; @@ -518,7 +518,7 @@ static bool keysReadKeysFromFile(void) FILE *keys_file = NULL; char *key = NULL, *value = NULL; char test_name[0x40] = {0}; - bool parse_fail = false; + bool parse_fail = false, eticket_rsa_kek_available = false; keys_file = fopen(KEYS_FILE_PATH, "rb"); if (!keys_file) @@ -538,12 +538,14 @@ static bool keysReadKeysFromFile(void) if (strlen(key) == 15 && !strcasecmp(key, "eticket_rsa_kek")) { if ((parse_fail = !keysParseHexKey(g_ncaKeyset.eticket_rsa_kek, key, value, sizeof(g_ncaKeyset.eticket_rsa_kek)))) break; + eticket_rsa_kek_available = true; key_count++; } else if (strlen(key) == 28 && !strcasecmp(key, "eticket_rsa_kek_personalized")) { /* This only appears on consoles that use the new PRODINFO key generation scheme. */ if ((parse_fail = !keysParseHexKey(g_ncaKeyset.eticket_rsa_kek_personalized, key, value, sizeof(g_ncaKeyset.eticket_rsa_kek_personalized)))) break; + eticket_rsa_kek_available = true; key_count++; } else { for(u32 i = 0; i < 0x20; i++) @@ -593,5 +595,11 @@ static bool keysReadKeysFromFile(void) return false; } + if (!eticket_rsa_kek_available) + { + LOGFILE("\"eticket_rsa_kek\" unavailable in \"%s\"!", KEYS_FILE_PATH); + return false; + } + return true; } diff --git a/source/legal_info.c b/source/legal_info.c index 274bdf2..e7fe7f9 100644 --- a/source/legal_info.c +++ b/source/legal_info.c @@ -81,6 +81,10 @@ bool legalInfoInitializeContext(LegalInfoContext *out, NcaContext *nca_ctx) /* Update NCA context pointer in output context. */ out->nca_ctx = nca_ctx; + /* Update content type context info in NCA context. */ + nca_ctx->content_type_ctx = out; + nca_ctx->content_type_ctx_patch = false; + success = true; end: diff --git a/source/mem.c b/source/mem.c index c075760..326a1a1 100644 --- a/source/mem.c +++ b/source/mem.c @@ -128,8 +128,9 @@ static bool memRetrieveProgramMemory(MemoryLocation *location, bool is_segment) mem_type = (u8)(mem_info.type & 0xFF); /* Code to allow for bitmasking segments. */ - if ((mem_info.perm & Perm_R) && ((!is_segment && mem_type != MemType_Unmapped && mem_type != MemType_Io && mem_type != MemType_ThreadLocal && mem_type != MemType_Reserved && !mem_info.attr) || \ - (is_segment && (mem_type == MemType_CodeStatic || mem_type == MemType_CodeMutable) && (((segment <<= 1) >> 1) & location->mask)))) + if ((mem_info.perm & Perm_R) && ((!is_segment && !mem_info.attr && (location->program_id != FS_SYSMODULE_TID || (location->program_id == FS_SYSMODULE_TID && mem_type != MemType_Unmapped && \ + mem_type != MemType_Io && mem_type != MemType_ThreadLocal && mem_type != MemType_Reserved))) || (is_segment && (mem_type == MemType_CodeStatic || mem_type == MemType_CodeMutable) && \ + (((segment <<= 1) >> 1) & location->mask)))) { /* If location->data == NULL, realloc() will essentially act as a malloc(). */ tmp = realloc(location->data, location->data_size + mem_info.size); diff --git a/source/mem.h b/source/mem.h index 64cfd02..eda7e71 100644 --- a/source/mem.h +++ b/source/mem.h @@ -27,6 +27,7 @@ #define FS_SYSMODULE_TID (u64)0x0100000000000000 #define BOOT_SYSMODULE_TID (u64)0x0100000000000005 #define SPL_SYSMODULE_TID (u64)0x0100000000000028 +#define ES_SYSMODULE_TID (u64)0x0100000000000033 typedef enum { MemoryProgramSegmentType_Text = BIT(0), @@ -46,8 +47,8 @@ typedef struct { bool memRetrieveProgramMemorySegment(MemoryLocation *location); /// Retrieves full memory data from a running program. -/// These are any type of memory pages with read permission (Perm_R) enabled. -/// MemType_Unmapped, MemType_Io, MemType_ThreadLocal and MemType_Reserved memory pages are excluded, as well as memory pages with a populated MemoryAttribute value. +/// These are any type of memory pages with read permission (Perm_R) enabled and no MemoryAttribute flag set. +/// MemType_Unmapped, MemType_Io, MemType_ThreadLocal and MemType_Reserved memory pages are excluded if FS program memory is being retrieved, in order to avoid hangs. bool memRetrieveFullProgramMemory(MemoryLocation *location); /// Frees a populated MemoryLocation element. diff --git a/source/nacp.c b/source/nacp.c index af21d43..eb32cb4 100644 --- a/source/nacp.c +++ b/source/nacp.c @@ -255,9 +255,6 @@ bool nacpInitializeContext(NacpContext *out, NcaContext *nca_ctx) /* Calculate SHA-256 checksum for the whole NACP. */ sha256CalculateHash(out->data_hash, out->data, sizeof(_NacpStruct)); - /* Save pointer to NCA context to the output NACP context. */ - out->nca_ctx = nca_ctx; - /* Retrieve NACP icon data. */ for(u8 i = 0; i < NacpSupportedLanguage_Count; i++) { @@ -317,6 +314,13 @@ bool nacpInitializeContext(NacpContext *out, NcaContext *nca_ctx) out->icon_count++; } + /* Update NCA context pointer in output context. */ + out->nca_ctx = nca_ctx; + + /* Update content type context info in NCA context. */ + nca_ctx->content_type_ctx = out; + nca_ctx->content_type_ctx_patch = false; + success = true; end: diff --git a/source/nca.c b/source/nca.c index 0849a2c..0869629 100644 --- a/source/nca.c +++ b/source/nca.c @@ -51,15 +51,11 @@ NX_INLINE bool ncaIsVersion0KeyAreaEncrypted(NcaContext *ctx); NX_INLINE u8 ncaGetKeyGenerationValue(NcaContext *ctx); NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx); -NX_INLINE void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset); -NX_INLINE void ncaUpdateAesCtrIv(u8 *ctr, u64 offset); -NX_INLINE void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset); - static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, bool lock); static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val, bool lock); static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, void *out, bool is_integrity_patch); -static void ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 patch_offset, u64 patch_size, void *buf, u64 buf_offset, u64 buf_size); +static bool ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 patch_size, u64 patch_offset, void *buf, u64 buf_size, u64 buf_offset); static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock); @@ -217,8 +213,8 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, if ((!out->rights_id_available || (out->rights_id_available && out->titlekey_retrieved)) && fs_ctx->encryption_type > NcaEncryptionType_None && \ fs_ctx->encryption_type <= NcaEncryptionType_AesCtrEx) { - /* Initialize section CTR. */ - ncaInitializeAesCtrIv(fs_ctx->ctr, fs_ctx->header.aes_ctr_upper_iv.value, fs_ctx->section_offset); + /* Initialize the partial AES counter for this section. */ + aes128CtrInitializePartialCtr(fs_ctx->ctr, fs_ctx->header.aes_ctr_upper_iv.value, fs_ctx->section_offset); /* Initialize AES context. */ if (out->rights_id_available) @@ -249,7 +245,7 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset) { if (!ctx || !*(ctx->content_id_str) || (ctx->storage_id != NcmStorageId_GameCard && !ctx->ncm_storage) || (ctx->storage_id == NcmStorageId_GameCard && !ctx->gamecard_offset) || !out || \ - !read_size || offset >= ctx->content_size || (offset + read_size) > ctx->content_size) + !read_size || (offset + read_size) > ctx->content_size) { LOGFILE("Invalid parameters!"); return false; @@ -297,13 +293,18 @@ bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *da void ncaWriteHierarchicalSha256PatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalSha256Patch *patch, void *buf, u64 buf_size, u64 buf_offset) { - if (!ctx || !*(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH || !patch || memcmp(patch->content_id.c, ctx->content_id.c, 0x10) != 0 || !patch->hash_region_count || \ - patch->hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT || !buf || !buf_size || buf_offset >= ctx->content_size || (buf_offset + buf_size) > ctx->content_size) return; + if (!ctx || !*(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH || !patch || patch->written || memcmp(patch->content_id.c, ctx->content_id.c, 0x10) != 0 || \ + !patch->hash_region_count || patch->hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT || !buf || !buf_size || (buf_offset + buf_size) > ctx->content_size) return; + + patch->written = true; for(u32 i = 0; i < patch->hash_region_count; i++) { NcaHashDataPatch *hash_region_patch = &(patch->hash_region_patch[i]); - ncaWritePatchToMemoryBuffer(ctx, hash_region_patch->data, hash_region_patch->offset, hash_region_patch->size, buf, buf_offset, buf_size); + if (hash_region_patch->written) continue; + + hash_region_patch->written = ncaWritePatchToMemoryBuffer(ctx, hash_region_patch->data, hash_region_patch->size, hash_region_patch->offset, buf, buf_size, buf_offset); + if (!hash_region_patch->written) patch->written = false; } } @@ -314,19 +315,34 @@ bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void void ncaWriteHierarchicalIntegrityPatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalIntegrityPatch *patch, void *buf, u64 buf_size, u64 buf_offset) { - if (!ctx || !*(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH || !patch || memcmp(patch->content_id.c, ctx->content_id.c, 0x10) != 0 || !buf || !buf_size || \ - buf_offset >= ctx->content_size || (buf_offset + buf_size) > ctx->content_size) return; + if (!ctx || !*(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH || !patch || patch->written || memcmp(patch->content_id.c, ctx->content_id.c, 0x10) != 0 || !buf || !buf_size || \ + (buf_offset + buf_size) > ctx->content_size) return; + + patch->written = true; for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++) { NcaHashDataPatch *hash_level_patch = &(patch->hash_level_patch[i]); - ncaWritePatchToMemoryBuffer(ctx, hash_level_patch->data, hash_level_patch->offset, hash_level_patch->size, buf, buf_offset, buf_size); + if (hash_level_patch->written) continue; + + hash_level_patch->written = ncaWritePatchToMemoryBuffer(ctx, hash_level_patch->data, hash_level_patch->size, hash_level_patch->offset, buf, buf_size, buf_offset); + if (!hash_level_patch->written) patch->written = false; } } +void ncaSetDownloadDistributionType(NcaContext *ctx) +{ + if (!ctx || ctx->content_size < NCA_FULL_HEADER_LENGTH || !*(ctx->content_id_str) || ctx->content_type > NcmContentType_DeltaFragment || \ + ctx->header.distribution_type == NcaDistributionType_Download) return; + LOGFILE("Setting download distribution type to %s NCA \"%s\".", titleGetNcmContentTypeName(ctx->content_type), ctx->content_id_str); + ctx->header.distribution_type = NcaDistributionType_Download; +} + void ncaRemoveTitlekeyCrypto(NcaContext *ctx) { - if (!ctx || ctx->content_size < NCA_FULL_HEADER_LENGTH || !ctx->rights_id_available || !ctx->titlekey_retrieved) return; + if (!ctx || ctx->content_size < NCA_FULL_HEADER_LENGTH || !*(ctx->content_id_str) || ctx->content_type > NcmContentType_DeltaFragment || !ctx->rights_id_available || !ctx->titlekey_retrieved) return; + + LOGFILE("Removing titlekey crypto from %s NCA \"%s\".", titleGetNcmContentTypeName(ctx->content_type), ctx->content_id_str); /* Copy decrypted titlekey to the decrypted NCA key area. */ /* This will be reencrypted at a later stage. */ @@ -413,11 +429,12 @@ void ncaWriteEncryptedHeaderDataToMemoryBuffer(NcaContext *ctx, void *buf, u64 b { /* Return right away if we're dealing with invalid parameters, or if the buffer data is not part of the range covered by the encrypted header data (NCA2/3 optimization, last condition). */ /* In order to avoid taking up too much execution time when this function is called (ideally inside a loop), we won't use ncaIsHeaderDirty() here. Let the user take care of it instead. */ - if (!ctx || !*(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH || !buf || !buf_size || buf_offset >= ctx->content_size || \ - (buf_offset + buf_size) > ctx->content_size || (ctx->format_version != NcaVersion_Nca0 && buf_offset >= NCA_FULL_HEADER_LENGTH)) return; + if (!ctx || ctx->content_size < NCA_FULL_HEADER_LENGTH || !buf || !buf_size || (buf_offset + buf_size) > ctx->content_size || \ + (ctx->format_version != NcaVersion_Nca0 && buf_offset >= NCA_FULL_HEADER_LENGTH)) return; /* Attempt to write the NCA header. */ - if (buf_offset < sizeof(NcaHeader)) ncaWritePatchToMemoryBuffer(ctx, &(ctx->encrypted_header), 0, sizeof(NcaHeader), buf, buf_offset, buf_size); + /* Return right away if the NCA header was only partially written. */ + if (buf_offset < sizeof(NcaHeader) && !ncaWritePatchToMemoryBuffer(ctx, &(ctx->encrypted_header), sizeof(NcaHeader), 0, buf, buf_size, buf_offset)) return; /* Attempt to write NCA FS section headers. */ for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++) @@ -426,7 +443,7 @@ void ncaWriteEncryptedHeaderDataToMemoryBuffer(NcaContext *ctx, void *buf, u64 b if (!fs_ctx->enabled) continue; u64 fs_header_offset = (ctx->format_version != NcaVersion_Nca0 ? (sizeof(NcaHeader) + (i * sizeof(NcaFsHeader))) : fs_ctx->section_offset); - ncaWritePatchToMemoryBuffer(ctx, &(fs_ctx->encrypted_header), fs_header_offset, sizeof(NcaFsHeader), buf, buf_offset, buf_size); + ncaWritePatchToMemoryBuffer(ctx, &(fs_ctx->encrypted_header), sizeof(NcaFsHeader), fs_header_offset, buf, buf_size, buf_offset); } } @@ -677,52 +694,6 @@ NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx) return rights_id_available; } -NX_INLINE void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset) -{ - if (!out || !ctr) return; - - offset >>= 4; - - for(u8 i = 0; i < 8; i++) - { - out[i] = ctr[0x8 - i - 1]; - out[0x10 - i - 1] = (u8)(offset & 0xFF); - offset >>= 8; - } -} - -NX_INLINE void ncaUpdateAesCtrIv(u8 *ctr, u64 offset) -{ - if (!ctr) return; - - offset >>= 4; - - for(u8 i = 0; i < 8; i++) - { - ctr[0x10 - i - 1] = (u8)(offset & 0xFF); - offset >>= 8; - } -} - -NX_INLINE void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset) -{ - if (!ctr) return; - - offset >>= 4; - - for(u8 i = 0; i < 8; i++) - { - ctr[0x10 - i - 1] = (u8)(offset & 0xFF); - offset >>= 8; - } - - for(u8 i = 0; i < 4; i++) - { - ctr[0x8 - i - 1] = (u8)(ctr_val & 0xFF); - ctr_val >>= 8; - } -} - static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, bool lock) { if (lock) mutexLock(&g_ncaCryptoBufferMutex); @@ -731,7 +702,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \ ctx->section_type >= NcaFsSectionType_Invalid || ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type > NcaEncryptionType_AesCtrEx || !out || !read_size || \ - offset >= ctx->section_size || (offset + read_size) > ctx->section_size) + (offset + read_size) > ctx->section_size) { LOGFILE("Invalid NCA FS section header parameters!"); goto end; @@ -747,8 +718,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 0; if (!*(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \ - (nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || content_offset >= nca_ctx->content_size || \ - (content_offset + read_size) > nca_ctx->content_size) + (nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || (content_offset + read_size) > nca_ctx->content_size) { LOGFILE("Invalid NCA header parameters!"); goto end; @@ -788,7 +758,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size } else if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx) { - ncaUpdateAesCtrIv(ctx->ctr, content_offset); + aes128CtrUpdatePartialCtr(ctx->ctr, content_offset); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr); aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size); } @@ -829,7 +799,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size } else if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx) { - ncaUpdateAesCtrIv(ctx->ctr, block_start_offset); + aes128CtrUpdatePartialCtr(ctx->ctr, block_start_offset); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr); aes128CtrCrypt(&(ctx->ctr_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size); } @@ -852,8 +822,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi bool ret = false; if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \ - ctx->section_type != NcaFsSectionType_PatchRomFs || ctx->encryption_type != NcaEncryptionType_AesCtrEx || !out || !read_size || offset >= ctx->section_size || \ - (offset + read_size) > ctx->section_size) + ctx->section_type != NcaFsSectionType_PatchRomFs || ctx->encryption_type != NcaEncryptionType_AesCtrEx || !out || !read_size || (offset + read_size) > ctx->section_size) { LOGFILE("Invalid NCA FS section header parameters!"); goto end; @@ -866,7 +835,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 0; if (!*(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \ - content_offset >= nca_ctx->content_size || (content_offset + read_size) > nca_ctx->content_size) + (content_offset + read_size) > nca_ctx->content_size) { LOGFILE("Invalid NCA header parameters!"); goto end; @@ -883,7 +852,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi } /* Decrypt data */ - ncaUpdateAesCtrExIv(ctx->ctr, ctr_val, content_offset); + aes128CtrUpdatePartialCtrEx(ctx->ctr, ctr_val, content_offset); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr); aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size); @@ -909,7 +878,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi } /* Decrypt data. */ - ncaUpdateAesCtrExIv(ctx->ctr, ctr_val, block_start_offset); + aes128CtrUpdatePartialCtrEx(ctx->ctr, ctr_val, block_start_offset); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr); aes128CtrCrypt(&(ctx->ctr_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size); @@ -949,7 +918,7 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, (is_integrity_patch && (ctx->header.hash_type != NcaHashType_HierarchicalIntegrity || \ !(layer_count = (ctx->header.hash_data.integrity_meta_info.info_level_hash.max_level_count - 1)) || layer_count != NCA_IVFC_LEVEL_COUNT || \ !(last_layer_size = ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[NCA_IVFC_LEVEL_COUNT - 1].size))) || !data || !data_size || \ - data_offset >= last_layer_size || (data_offset + data_size) > last_layer_size || !out) + (data_offset + data_size) > last_layer_size || !out) { LOGFILE("Invalid parameters!"); goto end; @@ -998,8 +967,8 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, } /* Validate layer properties. */ - if (hash_block_size <= 1 || cur_layer_offset >= ctx->section_size || !cur_layer_size || (cur_layer_offset + cur_layer_size) > ctx->section_size || \ - (i > 1 && (parent_layer_offset >= ctx->section_size || !parent_layer_size || (parent_layer_offset + parent_layer_size) > ctx->section_size))) + if (hash_block_size <= 1 || !cur_layer_size || (cur_layer_offset + cur_layer_size) > ctx->section_size || (i > 1 && (!parent_layer_size || \ + (parent_layer_offset + parent_layer_size) > ctx->section_size))) { LOGFILE("Invalid hierarchical parent/child layer!"); goto end; @@ -1143,11 +1112,11 @@ end: return success; } -static void ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 patch_offset, u64 patch_size, void *buf, u64 buf_offset, u64 buf_size) +static bool ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 patch_size, u64 patch_offset, void *buf, u64 buf_size, u64 buf_offset) { /* Return right away if we're dealing with invalid parameters, or if the buffer data is not part of the range covered by the patch (last two conditions). */ - if (!ctx || !patch || patch_offset >= ctx->content_size || !patch_size || (patch_offset + patch_size) > ctx->content_size || (buf_offset + buf_size) <= patch_offset || \ - (patch_offset + patch_size) <= buf_offset) return; + if (!ctx || !patch || !patch_size || (patch_offset + patch_size) > ctx->content_size || (buf_offset + buf_size) <= patch_offset || \ + (patch_offset + patch_size) <= buf_offset) return false; /* Overwrite buffer data using patch data. */ u64 patch_block_offset = (patch_offset < buf_offset ? (buf_offset - patch_offset) : 0); @@ -1161,6 +1130,8 @@ static void ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 memcpy((u8*)buf + buf_block_offset, (const u8*)patch + patch_block_offset, buf_block_size); LOGFILE("Overwrote 0x%lX bytes block at offset 0x%lX from raw NCA \"%s\" buffer (size 0x%lX, NCA offset 0x%lX).", buf_block_size, buf_block_offset, ctx->content_id_str, buf_size, buf_offset); + + return ((patch_block_offset + buf_block_size) == patch_size); } static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock) @@ -1172,7 +1143,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \ ctx->section_type >= NcaFsSectionType_Invalid || ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type >= NcaEncryptionType_AesCtrEx || !data || !data_size || \ - data_offset >= ctx->section_size || (data_offset + data_size) > ctx->section_size || !out_block_size || !out_block_offset) + (data_offset + data_size) > ctx->section_size || !out_block_size || !out_block_offset) { LOGFILE("Invalid NCA FS section header parameters!"); goto end; @@ -1188,8 +1159,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const u64 plain_chunk_offset = 0; if (!*(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \ - (nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || content_offset >= nca_ctx->content_size || \ - (content_offset + data_size) > nca_ctx->content_size) + (nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || (content_offset + data_size) > nca_ctx->content_size) { LOGFILE("Invalid NCA header parameters!"); goto end; @@ -1225,7 +1195,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const } else if (ctx->encryption_type == NcaEncryptionType_AesCtr) { - ncaUpdateAesCtrIv(ctx->ctr, content_offset); + aes128CtrUpdatePartialCtr(ctx->ctr, content_offset); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr); aes128CtrCrypt(&(ctx->ctr_ctx), out, out, data_size); } @@ -1277,7 +1247,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const } else if (ctx->encryption_type == NcaEncryptionType_AesCtr) { - ncaUpdateAesCtrIv(ctx->ctr, content_offset); + aes128CtrUpdatePartialCtr(ctx->ctr, content_offset); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr); aes128CtrCrypt(&(ctx->ctr_ctx), out, out, block_size); } diff --git a/source/nca.h b/source/nca.h index 2322d6e..4194a62 100644 --- a/source/nca.h +++ b/source/nca.h @@ -325,21 +325,26 @@ typedef struct { NcaFsSectionContext fs_ctx[NCA_FS_HEADER_COUNT]; NcaDecryptedKeyArea decrypted_key_area; void *content_type_ctx; ///< Pointer to a content type context (e.g. ContentMetaContext, ProgramInfoContext, NacpContext, LegalInfoContext). Set to NULL if unused. + bool content_type_ctx_patch; ///< Set to true if a NCA patch generated by the content type context is needed and hasn't been completely writen yet. + u32 content_type_ctx_data_idx; ///< Start index for the data generated by the content type context. Used while creating NSPs. } NcaContext; typedef struct { - u64 offset; ///< New data offset (relative to the start of the NCA content file). - u64 size; ///< New data size. - u8 *data; ///< New data. + bool written; ///< Set to true if this patch has already been written. + u64 offset; ///< New data offset (relative to the start of the NCA content file). + u64 size; ///< New data size. + u8 *data; ///< New data. } NcaHashDataPatch; typedef struct { + bool written; ///< Set to true if all hash region patches have been written. NcmContentId content_id; u32 hash_region_count; NcaHashDataPatch hash_region_patch[NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT]; } NcaHierarchicalSha256Patch; typedef struct { + bool written; ///< Set to true if all hash level patches have been written. NcmContentId content_id; NcaHashDataPatch hash_level_patch[NCA_IVFC_LEVEL_COUNT]; } NcaHierarchicalIntegrityPatch; @@ -386,6 +391,7 @@ bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *da /// Overwrites block(s) from a buffer holding raw NCA data using previously initialized NcaContext and NcaHierarchicalSha256Patch. /// 'buf_offset' must hold the raw NCA offset where the data stored in 'buf' was read from. +/// The 'written' fields from the input NcaHierarchicalSha256Patch and its underlying NcaHashDataPatch elements are updated by this function. void ncaWriteHierarchicalSha256PatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalSha256Patch *patch, void *buf, u64 buf_size, u64 buf_offset); /// Generates HierarchicalIntegrity FS section patch data, which can be used to seamlessly replace NCA data. @@ -396,8 +402,13 @@ bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void /// Overwrites block(s) from a buffer holding raw NCA data using a previously initialized NcaContext and NcaHierarchicalIntegrityPatch. /// 'buf_offset' must hold the raw NCA offset where the data stored in 'buf' was read from. +/// The 'written' fields from the input NcaHierarchicalIntegrityPatch and its underlying NcaHashDataPatch elements are updated by this function. void ncaWriteHierarchicalIntegrityPatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalIntegrityPatch *patch, void *buf, u64 buf_size, u64 buf_offset); +/// Sets the distribution type field from the underlying NCA header in the provided NCA context to NcaDistributionType_Download. +/// Needed for NSP dumps from gamecard titles. +void ncaSetDownloadDistributionType(NcaContext *ctx); + /// Removes titlekey crypto dependency from a NCA context by wiping the Rights ID from the underlying NCA header and copying the decrypted titlekey to the NCA key area. void ncaRemoveTitlekeyCrypto(NcaContext *ctx); @@ -420,12 +431,6 @@ const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx); /// Helper inline functions. -NX_INLINE void ncaSetDownloadDistributionType(NcaContext *ctx) -{ - if (!ctx || ctx->header.distribution_type == NcaDistributionType_Download) return; - ctx->header.distribution_type = NcaDistributionType_Download; -} - NX_INLINE bool ncaIsHeaderDirty(NcaContext *ctx) { if (!ctx) return false; @@ -442,7 +447,7 @@ NX_INLINE bool ncaValidateHierarchicalSha256Offsets(NcaHierarchicalSha256Data *h for(u32 i = 0; i < hierarchical_sha256_data->hash_region_count; i++) { NcaRegion *hash_region = &(hierarchical_sha256_data->hash_region[i]); - if (hash_region->offset >= section_size || !hash_region->size || (hash_region->offset + hash_region->size) > section_size) return false; + if (!hash_region->size || (hash_region->offset + hash_region->size) > section_size) return false; } return true; @@ -456,7 +461,7 @@ NX_INLINE bool ncaValidateHierarchicalIntegrityOffsets(NcaIntegrityMetaInfo *int for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++) { NcaHierarchicalIntegrityVerificationLevelInformation *level_information = &(integrity_meta_info->info_level_hash.level_information[i]); - if (level_information->offset >= section_size || !level_information->size || !level_information->block_order || (level_information->offset + level_information->size) > section_size) return false; + if (!level_information->size || !level_information->block_order || (level_information->offset + level_information->size) > section_size) return false; } return true; diff --git a/source/npdm.c b/source/npdm.c index 0ee1a46..10418eb 100644 --- a/source/npdm.c +++ b/source/npdm.c @@ -70,9 +70,6 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx goto end; } - /* Calculate SHA-256 checksum for the whole raw NPDM. */ - sha256CalculateHash(out->raw_data_hash, out->raw_data, out->raw_data_size); - /* Verify meta header. */ out->meta_header = (NpdmMetaHeader*)out->raw_data; cur_offset += sizeof(NpdmMetaHeader); @@ -281,5 +278,34 @@ bool npdmChangeAcidPublicKeyAndNcaSignature(NpdmContext *npdm_ctx) return false; } + /* Generate Partition FS entry patch. */ + if (!pfsGenerateEntryPatch(npdm_ctx->pfs_ctx, npdm_ctx->pfs_entry, npdm_ctx->raw_data, npdm_ctx->raw_data_size, 0, &(npdm_ctx->nca_patch))) + { + LOGFILE("Failed to generate Partition FS entry patch!"); + return false; + } + + /* Update NCA content type context patch status. */ + nca_ctx->content_type_ctx_patch = true; + return true; } + +void npdmWriteNcaPatch(NpdmContext *npdm_ctx, void *buf, u64 buf_size, u64 buf_offset) +{ + NcaContext *nca_ctx = NULL; + + /* Using npdmIsValidContext() here would probably take up precious CPU cycles. */ + if (!npdm_ctx || !npdm_ctx->pfs_ctx || !npdm_ctx->pfs_ctx->nca_fs_ctx || !(nca_ctx = (NcaContext*)npdm_ctx->pfs_ctx->nca_fs_ctx->nca_ctx) || nca_ctx->content_type != NcmContentType_Program || \ + !nca_ctx->content_type_ctx_patch || npdm_ctx->nca_patch.written) return; + + /* Attempt to write Partition FS entry. */ + pfsWriteEntryPatchToMemoryBuffer(npdm_ctx->pfs_ctx, &(npdm_ctx->nca_patch), buf, buf_size, buf_offset); + + /* Check if we need to update the NCA content type context patch status. */ + if (npdm_ctx->nca_patch.written) + { + nca_ctx->content_type_ctx_patch = false; + LOGFILE("NPDM Partition FS file entry patch successfully written to NCA \"%s\"!", nca_ctx->content_id_str); + } +} diff --git a/source/npdm.h b/source/npdm.h index af6fd2d..db544f2 100644 --- a/source/npdm.h +++ b/source/npdm.h @@ -540,7 +540,6 @@ typedef struct { ///< Bear in mind that generating a patch modifies the NCA context. u8 *raw_data; ///< Pointer to a dynamically allocated buffer that holds the raw NPDM. u64 raw_data_size; ///< Raw NPDM size. Kept here for convenience - this is part of 'pfs_entry'. - u8 raw_data_hash[SHA256_HASH_SIZE]; ///< SHA-256 checksum calculated over the whole raw NPDM. Used to determine if NcaHierarchicalSha256Patch generation is truly needed. NpdmMetaHeader *meta_header; ///< Pointer to the NpdmMetaHeader within 'raw_data'. NpdmAcidHeader *acid_header; ///< Pointer to the NpdmAcidHeader within 'raw_data'. NpdmFsAccessControlDescriptor *acid_fac_descriptor; ///< Pointer to the NpdmFsAccessControlDescriptor within the NPDM ACID section. @@ -555,9 +554,12 @@ typedef struct { /// Initializes a NpdmContext using a previously initialized PartitionFileSystemContext (which must belong to the ExeFS from a Program NCA). bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx); -/// Changes the ACID public key from the NPDM in the input NpdmContext and updates the ACID signature from the NCA header in the underlying NCA context. +/// Changes the ACID public key from the NPDM in the input NpdmContext, updates the ACID signature from the NCA header in the underlying NCA context and generates a Partition FS entry patch. bool npdmChangeAcidPublicKeyAndNcaSignature(NpdmContext *npdm_ctx); +/// Writes data from the Partition FS patch in the input NpdmContext to the provided buffer. +void npdmWriteNcaPatch(NpdmContext *npdm_ctx, void *buf, u64 buf_size, u64 buf_offset); + /// Helper inline functions. NX_INLINE void npdmFreeContext(NpdmContext *npdm_ctx) @@ -578,19 +580,6 @@ NX_INLINE bool npdmIsValidContext(NpdmContext *npdm_ctx) ((npdm_ctx->aci_header->kernel_capability_size && npdm_ctx->aci_kc_descriptor) || (!npdm_ctx->aci_header->kernel_capability_size && !npdm_ctx->aci_kc_descriptor))); } -NX_INLINE bool npdmIsNcaPatchRequired(NpdmContext *npdm_ctx) -{ - if (!npdmIsValidContext(npdm_ctx)) return false; - u8 tmp_hash[SHA256_HASH_SIZE] = {0}; - sha256CalculateHash(tmp_hash, npdm_ctx->raw_data, npdm_ctx->raw_data_size); - return (memcmp(tmp_hash, npdm_ctx->raw_data_hash, SHA256_HASH_SIZE) != 0); -} - -NX_INLINE bool npdmGenerateNcaPatch(NpdmContext *npdm_ctx) -{ - return (npdmIsValidContext(npdm_ctx) && pfsGenerateEntryPatch(npdm_ctx->pfs_ctx, npdm_ctx->pfs_entry, npdm_ctx->raw_data, npdm_ctx->raw_data_size, 0, &(npdm_ctx->nca_patch))); -} - NX_INLINE u32 npdmGetKernelCapabilityDescriptorEntryValue(NpdmKernelCapabilityDescriptorEntry *entry) { return (entry ? (((entry->value + 1) & ~entry->value) - 1) : 0); diff --git a/source/pfs.c b/source/pfs.c index 8856c66..a762640 100644 --- a/source/pfs.c +++ b/source/pfs.c @@ -22,15 +22,11 @@ #include "pfs.h" #include "npdm.h" +#define PFS_FULL_HEADER_ALIGNMENT 0x20 + bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx) { - if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || nca_fs_ctx->section_type != NcaFsSectionType_PartitionFs || nca_fs_ctx->header.fs_type != NcaFsType_PartitionFs || \ - nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalSha256) - { - LOGFILE("Invalid parameters!"); - return false; - } - + NcaContext *nca_ctx = NULL; u32 magic = 0; PartitionFileSystemHeader pfs_header = {0}; @@ -39,6 +35,13 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext * u32 hash_region_count = 0; NcaRegion *hash_region = NULL; + if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || nca_fs_ctx->section_type != NcaFsSectionType_PartitionFs || nca_fs_ctx->header.fs_type != NcaFsType_PartitionFs || \ + nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalSha256 || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || (nca_ctx->rights_id_available && !nca_ctx->titlekey_retrieved)) + { + LOGFILE("Invalid parameters!"); + return false; + } + /* Free output context beforehand. */ pfsFreeContext(out); @@ -105,7 +108,7 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext * bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_size, u64 offset) { - if (!ctx || !ctx->nca_fs_ctx || !ctx->size || !out || !read_size || offset >= ctx->size || (offset + read_size) > ctx->size) + if (!ctx || !ctx->nca_fs_ctx || !ctx->size || !out || !read_size || (offset + read_size) > ctx->size) { LOGFILE("Invalid parameters!"); return false; @@ -123,8 +126,7 @@ bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_s bool pfsReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, void *out, u64 read_size, u64 offset) { - if (!ctx || !fs_entry || fs_entry->offset >= ctx->size || !fs_entry->size || (fs_entry->offset + fs_entry->size) > ctx->size || !out || !read_size || offset >= fs_entry->size || \ - (offset + read_size) > fs_entry->size) + if (!ctx || !fs_entry || !fs_entry->size || (fs_entry->offset + fs_entry->size) > ctx->size || !out || !read_size || (offset + read_size) > fs_entry->size) { LOGFILE("Invalid parameters!"); return false; @@ -204,8 +206,8 @@ bool pfsGetTotalDataSize(PartitionFileSystemContext *ctx, u64 *out_size) bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out) { - if (!ctx || !ctx->nca_fs_ctx || !ctx->header_size || !ctx->header || !fs_entry || fs_entry->offset >= ctx->size || !fs_entry->size || (fs_entry->offset + fs_entry->size) > ctx->size || !data || \ - !data_size || data_offset >= fs_entry->size || (data_offset + data_size) > fs_entry->size || !out) + if (!ctx || !ctx->nca_fs_ctx || !ctx->header_size || !ctx->header || !fs_entry || !fs_entry->size || (fs_entry->offset + fs_entry->size) > ctx->size || !data || !data_size || \ + (data_offset + data_size) > fs_entry->size || !out) { LOGFILE("Invalid parameters!"); return false; @@ -221,3 +223,142 @@ bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemE return true; } + +bool pfsAddEntryInformationToFileContext(PartitionFileSystemFileContext *ctx, const char *entry_name, u64 entry_size, u32 *out_entry_idx) +{ + if (!ctx || !entry_name || !*entry_name) + { + LOGFILE("Invalid parameters!"); + return false; + } + + PartitionFileSystemHeader *header = &(ctx->header); + + PartitionFileSystemEntry *tmp_pfs_entries = NULL, *cur_pfs_entry = NULL, *prev_pfs_entry = NULL; + u64 tmp_pfs_entries_size = ((header->entry_count + 1) * sizeof(PartitionFileSystemEntry)); + + char *tmp_name_table = NULL; + u32 tmp_name_table_size = (header->name_table_size + strlen(entry_name) + 1); + + /* Reallocate Partition FS entries. */ + if (!(tmp_pfs_entries = realloc(ctx->entries, tmp_pfs_entries_size))) + { + LOGFILE("Failed to reallocate Partition FS entries!"); + return false; + } + + ctx->entries = tmp_pfs_entries; + tmp_pfs_entries = NULL; + + /* Update Partition FS entry information. */ + cur_pfs_entry = &(ctx->entries[header->entry_count]); + prev_pfs_entry = (header->entry_count ? &(ctx->entries[header->entry_count - 1]) : NULL); + + memset(cur_pfs_entry, 0, sizeof(PartitionFileSystemEntry)); + + cur_pfs_entry->offset = (prev_pfs_entry ? (prev_pfs_entry->offset + prev_pfs_entry->size) : 0); + cur_pfs_entry->size = entry_size; + cur_pfs_entry->name_offset = header->name_table_size; + + /* Reallocate Partition FS name table. */ + if (!(tmp_name_table = realloc(ctx->name_table, tmp_name_table_size))) + { + LOGFILE("Failed to reallocate Partition FS name table!"); + return false; + } + + ctx->name_table = tmp_name_table; + tmp_name_table = NULL; + + /* Update Partition FS name table. */ + sprintf(ctx->name_table + header->name_table_size, "%s", entry_name); + header->name_table_size = tmp_name_table_size; + + /* Update output entry index. */ + if (out_entry_idx) *out_entry_idx = header->entry_count; + + /* Update Partition FS entry count, name table size and data size. */ + header->entry_count++; + ctx->fs_size += entry_size; + + return true; +} + +bool pfsUpdateEntryNameFromFileContext(PartitionFileSystemFileContext *ctx, u32 entry_idx, const char *new_entry_name) +{ + if (!ctx || !ctx->header.entry_count || !ctx->header.name_table_size || !ctx->entries || !ctx->name_table || entry_idx >= ctx->header.entry_count || !new_entry_name || !*new_entry_name) + { + LOGFILE("Invalid parameters!"); + return false; + } + + PartitionFileSystemEntry *pfs_entry = &(ctx->entries[entry_idx]); + + char *name_table_entry = (ctx->name_table + pfs_entry->name_offset); + size_t new_entry_name_len = strlen(new_entry_name); + size_t cur_entry_name_len = strlen(name_table_entry); + + if (new_entry_name_len > cur_entry_name_len) + { + LOGFILE("New entry name length exceeds previous entry name length! (0x%lX > 0x%lX).", new_entry_name_len, cur_entry_name_len); + return false; + } + + memcpy(name_table_entry, new_entry_name, new_entry_name_len); + + return true; +} + +bool pfsWriteFileContextHeaderToMemoryBuffer(PartitionFileSystemFileContext *ctx, void *buf, u64 buf_size, u64 *out_header_size) +{ + if (!ctx || !ctx->header.entry_count || !ctx->header.name_table_size || !ctx->entries || !ctx->name_table || !buf || !out_header_size) + { + LOGFILE("Invalid parameters!"); + return false; + } + + PartitionFileSystemHeader *header = &(ctx->header); + u8 *buf_u8 = (u8*)buf; + u64 header_size = 0, full_header_size = 0, block_offset = 0, block_size = 0; + u32 padding_size = 0; + + /* Calculate header size. */ + header_size = (sizeof(PartitionFileSystemHeader) + (header->entry_count * sizeof(PartitionFileSystemEntry)) + header->name_table_size); + + /* Calculate full header size and padding size. */ + full_header_size = (IS_ALIGNED(header_size, PFS_FULL_HEADER_ALIGNMENT) ? ALIGN_UP(header_size + 1, PFS_FULL_HEADER_ALIGNMENT) : ALIGN_UP(header_size, PFS_FULL_HEADER_ALIGNMENT)); + padding_size = (u32)(full_header_size - header_size); + + /* Check buffer size. */ + if (buf_size < full_header_size) + { + LOGFILE("Not enough space available in input buffer to write full Partition FS header! (got 0x%lX, need 0x%lX).", buf_size, full_header_size); + return false; + } + + /* Update name table size in Partition FS header to make it reflect the padding. */ + header->name_table_size += padding_size; + + /* Write full header. */ + block_size = sizeof(PartitionFileSystemHeader); + memcpy(buf_u8 + block_offset, header, block_size); + block_offset += block_size; + + block_size = (header->entry_count * sizeof(PartitionFileSystemEntry)); + memcpy(buf_u8 + block_offset, ctx->entries, block_size); + block_offset += block_size; + + block_size = header->name_table_size; + memcpy(buf_u8 + block_offset, ctx->name_table, block_size); + block_offset += block_size; + + memset(buf_u8 + block_offset, 0, padding_size); + + /* Update output header size. */ + *out_header_size = full_header_size; + + /* Restore name table size in Partition FS header. */ + header->name_table_size -= padding_size; + + return true; +} diff --git a/source/pfs.h b/source/pfs.h index d07fc67..4d82c17 100644 --- a/source/pfs.h +++ b/source/pfs.h @@ -41,6 +41,7 @@ typedef struct { u8 reserved[0x4]; } PartitionFileSystemEntry; +/// Used with Partition FS sections from NCAs. typedef struct { NcaFsSectionContext *nca_fs_ctx; ///< Used to read NCA FS section data. u64 offset; ///< Partition offset (relative to the start of the NCA FS section). @@ -50,6 +51,14 @@ typedef struct { u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table. } PartitionFileSystemContext; +/// Used with Partition FS images (e.g. NSPs). +typedef struct { + PartitionFileSystemHeader header; ///< Partition FS header. Holds the entry count and name table size. + PartitionFileSystemEntry *entries; ///< Partition FS entries. + char *name_table; ///< Name table. + u64 fs_size; ///< Partition FS data size. Updated each time a new entry is added. +} PartitionFileSystemFileContext; + /// Initializes a Partition FS context. bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx); @@ -73,6 +82,16 @@ bool pfsGetTotalDataSize(PartitionFileSystemContext *ctx, u64 *out_size); /// Use the pfsWriteEntryPatchToMemoryBuffer() wrapper to write patch data generated by this function. bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out); +/// Adds a new Partition FS entry to an existing PartitionFileSystemFileContext, using the provided entry name and size. +/// If 'out_entry_idx' is a valid pointer, the index to the new Partition FS entry will be saved to it. +bool pfsAddEntryInformationToFileContext(PartitionFileSystemFileContext *ctx, const char *entry_name, u64 entry_size, u32 *out_entry_idx); + +/// Updates the name from a Partition FS entry in an existing PartitionFileSystemFileContext, using an entry index and the new entry name. +bool pfsUpdateEntryNameFromFileContext(PartitionFileSystemFileContext *ctx, u32 entry_idx, const char *new_entry_name); + +/// Generates a full Partition FS header from an existing PartitionFileSystemFileContext and writes it to the provided memory buffer. +bool pfsWriteFileContextHeaderToMemoryBuffer(PartitionFileSystemFileContext *ctx, void *buf, u64 buf_size, u64 *out_header_size); + /// Miscellaneous functions. NX_INLINE void pfsFreeContext(PartitionFileSystemContext *ctx) @@ -134,4 +153,19 @@ NX_INLINE void pfsFreeEntryPatch(NcaHierarchicalSha256Patch *patch) ncaFreeHierarchicalSha256Patch(patch); } +NX_INLINE void pfsFreeFileContext(PartitionFileSystemFileContext *ctx) +{ + if (!ctx) return; + if (ctx->entries) free(ctx->entries); + if (ctx->name_table) free(ctx->name_table); + memset(ctx, 0, sizeof(PartitionFileSystemFileContext)); +} + +NX_INLINE void pfsInitializeFileContext(PartitionFileSystemFileContext *ctx) +{ + if (!ctx) return; + pfsFreeFileContext(ctx); + ctx->header.magic = __builtin_bswap32(PFS0_MAGIC); +} + #endif /* __PFS_H__ */ diff --git a/source/program_info.c b/source/program_info.c index 9c9b459..5e632a8 100644 --- a/source/program_info.c +++ b/source/program_info.c @@ -140,6 +140,10 @@ bool programInfoInitializeContext(ProgramInfoContext *out, NcaContext *nca_ctx) /* Update output context. */ out->nca_ctx = nca_ctx; + /* Update content type context info in NCA context. */ + nca_ctx->content_type_ctx = out; + nca_ctx->content_type_ctx_patch = false; + success = true; end: diff --git a/source/program_info.h b/source/program_info.h index 815ba1a..210cfba 100644 --- a/source/program_info.h +++ b/source/program_info.h @@ -75,14 +75,9 @@ NX_INLINE bool programInfoChangeAcidPublicKeyAndNcaSignature(ProgramInfoContext return (programInfoIsValidContext(program_info_ctx) && npdmChangeAcidPublicKeyAndNcaSignature(&(program_info_ctx->npdm_ctx))); } -NX_INLINE bool programInfoIsNcaPatchRequired(ProgramInfoContext *program_info_ctx) +NX_INLINE void programInfoWriteNcaPatch(ProgramInfoContext *program_info_ctx, void *buf, u64 buf_size, u64 buf_offset) { - return (programInfoIsValidContext(program_info_ctx) && npdmIsNcaPatchRequired(&(program_info_ctx->npdm_ctx))); -} - -NX_INLINE bool programInfoGenerateNcaPatch(ProgramInfoContext *program_info_ctx) -{ - return (programInfoIsValidContext(program_info_ctx) && npdmGenerateNcaPatch(&(program_info_ctx->npdm_ctx))); + if (program_info_ctx) npdmWriteNcaPatch(&(program_info_ctx->npdm_ctx), buf, buf_size, buf_offset); } #endif /* __PROGRAM_INFO_H__ */ diff --git a/source/romfs.c b/source/romfs.c index e310634..f718de7 100644 --- a/source/romfs.c +++ b/source/romfs.c @@ -33,7 +33,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_ if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || (nca_ctx->format_version == NcaVersion_Nca0 && \ (nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalSha256)) || (nca_ctx->format_version != NcaVersion_Nca0 && \ - (nca_fs_ctx->section_type != NcaFsSectionType_RomFs || nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalIntegrity))) + (nca_fs_ctx->section_type != NcaFsSectionType_RomFs || nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalIntegrity)) || (nca_ctx->rights_id_available && !nca_ctx->titlekey_retrieved)) { LOGFILE("Invalid parameters!"); return false; @@ -96,7 +96,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_ dir_table_offset = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.directory_entry_offset : out->header.cur_format.directory_entry_offset); out->dir_table_size = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.directory_entry_size : out->header.cur_format.directory_entry_size); - if (!out->dir_table_size || dir_table_offset >= out->size || (dir_table_offset + out->dir_table_size) > out->size) + if (!out->dir_table_size || (dir_table_offset + out->dir_table_size) > out->size) { LOGFILE("Invalid RomFS directory entries table!"); return false; @@ -119,7 +119,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_ file_table_offset = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.file_entry_offset : out->header.cur_format.file_entry_offset); out->file_table_size = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.file_entry_size : out->header.cur_format.file_entry_size); - if (!out->file_table_size || file_table_offset >= out->size || (file_table_offset + out->file_table_size) > out->size) + if (!out->file_table_size || (file_table_offset + out->file_table_size) > out->size) { LOGFILE("Invalid RomFS file entries table!"); goto end; @@ -156,7 +156,7 @@ end: bool romfsReadFileSystemData(RomFileSystemContext *ctx, void *out, u64 read_size, u64 offset) { - if (!ctx || !ctx->nca_fs_ctx || !ctx->size || !out || !read_size || offset >= ctx->size || (offset + read_size) > ctx->size) + if (!ctx || !ctx->nca_fs_ctx || !ctx->size || !out || !read_size || (offset + read_size) > ctx->size) { LOGFILE("Invalid parameters!"); return false; @@ -174,8 +174,7 @@ bool romfsReadFileSystemData(RomFileSystemContext *ctx, void *out, u64 read_size bool romfsReadFileEntryData(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, void *out, u64 read_size, u64 offset) { - if (!ctx || !ctx->body_offset || !file_entry || !file_entry->size || file_entry->offset >= ctx->size || (file_entry->offset + file_entry->size) > ctx->size || \ - !out || !read_size || offset >= file_entry->size || (offset + read_size) > file_entry->size) + if (!ctx || !ctx->body_offset || !file_entry || !file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !out || !read_size || (offset + read_size) > file_entry->size) { LOGFILE("Invalid parameters!"); return false; @@ -507,8 +506,7 @@ bool romfsGeneratePathFromFileEntry(RomFileSystemContext *ctx, RomFileSystemFile bool romfsGenerateFileEntryPatch(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, const void *data, u64 data_size, u64 data_offset, RomFileSystemFileEntryPatch *out) { if (!ctx || !ctx->nca_fs_ctx || !ctx->body_offset || (ctx->nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs && ctx->nca_fs_ctx->section_type != NcaFsSectionType_RomFs) || !file_entry || \ - !file_entry->size || file_entry->offset >= ctx->size || (file_entry->offset + file_entry->size) > ctx->size || !data || !data_size || data_offset >= file_entry->size || \ - (data_offset + data_size) > file_entry->size || !out) + !file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !data || !data_size || (data_offset + data_size) > file_entry->size || !out) { LOGFILE("Invalid parameters!"); return false; diff --git a/source/save.c b/source/save.c index ba1e11b..c4c35e2 100644 --- a/source/save.c +++ b/source/save.c @@ -1746,6 +1746,32 @@ save_ctx_t *save_open_savefile(const char *path, u32 action) open_savefile = true; + /* Code to dump the requested file in its entirety. Useful to retrieve protected system savefiles without exiting HOS. */ + /*char sd_path[FS_MAX_PATH] = {0}; + sprintf(sd_path, "sdmc:/%s", strrchr(path, '/') + 1); + + UINT blksize = 0x100000; + u8 *buf = malloc(blksize); + FILE *fd = fopen(sd_path, "wb"); + + if (buf && fd) + { + u64 size = f_size(save_fd); + UINT br = 0; + + for(u64 i = 0; i < size; i += blksize) + { + if ((size - i) < blksize) blksize = (size - i); + if (f_read(save_fd, buf, blksize, &br) != FR_OK || br != blksize) break; + fwrite(buf, 1, blksize, fd); + } + + f_rewind(save_fd); + } + + if (fd) fclose(fd); + if (buf) free(buf);*/ + save_ctx = calloc(1, sizeof(save_ctx_t)); if (!save_ctx) { diff --git a/source/tik.c b/source/tik.c index 49f6559..8258533 100644 --- a/source/tik.c +++ b/source/tik.c @@ -1,7 +1,7 @@ /* * tik.c * - * Copyright (c) 2019, shchmue. + * Copyright (c) 2019-2020, shchmue. * Copyright (c) 2020, DarkMatterCore . * * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). @@ -27,6 +27,8 @@ #include "keys.h" #include "rsa.h" #include "gamecard.h" +#include "mem.h" +#include "aes.h" #define TIK_COMMON_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e1" #define TIK_PERSONALIZED_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e2" @@ -34,6 +36,8 @@ #define ETICKET_DEVKEY_PUBLIC_EXPONENT 0x10001 +#define ES_CTRKEY_ENTRY_ALIGNMENT 0x8 + /* Type definitions. */ /// Everything after the AES CTR is encrypted. @@ -47,6 +51,22 @@ typedef struct { u8 ghash[0x10]; } tikEticketDeviceKeyData; +/// 9.x+ CTR key entry in ES .data segment. Used to store CTR key/IV data for encrypted volatile tickets in ticket.bin and/or encrypted entries in ticket_list.bin. +/// This is always stored in pairs. The first entry holds the key/IV for the encrypted volatile ticket, while the second entry holds the key/IV for the encrypted entry in ticket_list.bin. +/// First index in this list is always 0, and it's aligned to ES_CTRKEY_ENTRY_ALIGNMENT. +typedef struct { + u32 idx; ///< Entry index. + u8 key[AES_BLOCK_SIZE]; ///< AES-128-CTR key. + u8 ctr[AES_BLOCK_SIZE]; ///< AES-128-CTR counter/IV. Always zeroed out. +} tikEsCtrKeyEntry9x; + +/// Lookup pattern for tikEsCtrKeyEntry9x. +typedef struct { + u32 idx1; ///< Always set to 0 (first entry). + u8 ctrdata[AES_BLOCK_SIZE * 2]; + u32 idx2; ///< Always set to 1 (second entry). +} tikEsCtrKeyPattern9x; + /* Global variables. */ static SetCalRsa2048DeviceKey g_eTicketDeviceKey = {0}; @@ -59,6 +79,18 @@ static const u8 g_nullHash[0x20] = { 0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55 }; +static const char *g_tikTitleKeyTypeStrings[] = { + [TikTitleKeyType_Common] = "common", + [TikTitleKeyType_Personalized] = "personalized" +}; + +static MemoryLocation g_esMemoryLocation = { + .program_id = ES_SYSMODULE_TID, + .mask = 0, + .data = NULL, + .data_size = 0 +}; + /* Function prototypes. */ static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsId *id); @@ -69,6 +101,7 @@ static bool tikGetTitleKekDecryptedTitleKey(void *dst, const void *src, u8 key_g static bool tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out); static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, bool personalized); +static u8 *tikRetrieveTicketEntryFromTicketBin(allocation_table_storage_ctx_t *fat_storage, u64 ticket_bin_size, u8 *buf, u64 buf_size, const FsRightsId *id, u8 titlekey_type); static bool tikGetTicketTypeAndSize(void *data, u64 data_size, u8 *out_type, u64 *out_size); @@ -83,10 +116,12 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gam return false; } + TikCommonBlock *tik_common_block = NULL; + /* Check if this ticket has already been retrieved. */ if (dst->type > TikType_None && dst->type <= TikType_SigHmac160 && dst->size >= SIGNED_TIK_MIN_SIZE && dst->size <= SIGNED_TIK_MAX_SIZE) { - TikCommonBlock *tik_common_block = tikGetCommonBlock(dst->data); + tik_common_block = tikGetCommonBlock(dst->data); if (tik_common_block && !memcmp(tik_common_block->rights_id.c, id->c, 0x10)) return true; } @@ -118,6 +153,10 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gam return false; } + /* Generate rights ID string. */ + tik_common_block = tikGetCommonBlock(dst->data); + utilsGenerateHexStringFromData(dst->rights_id_str, sizeof(dst->rights_id_str), tik_common_block->rights_id.c, sizeof(tik_common_block->rights_id.c)); + return true; } @@ -178,6 +217,7 @@ bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_c /* Update the rest of the ticket fields. */ tik_common_block->titlekey_type = TikTitleKeyType_Common; + tik_common_block->property_mask &= ~(TikPropertyMask_ELicenseRequired | TikPropertyMask_Volatile); tik_common_block->ticket_id = 0; tik_common_block->device_id = 0; tik_common_block->account_id = 0; @@ -245,7 +285,6 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight return false; } - u32 i; u8 titlekey_type = 0; save_ctx_t *save_ctx = NULL; @@ -253,10 +292,9 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight u64 ticket_bin_size = 0; u64 buf_size = (SIGNED_TIK_MAX_SIZE * 0x10); - u64 br = 0, total_br = 0; - u8 *ticket_bin_buf = NULL; + u8 *buf = NULL, *ticket_entry = NULL; - bool found_tik = false, success = false; + bool success = false; if (!tikGetTitleKeyTypeFromRightsId(id, &titlekey_type)) { @@ -267,77 +305,47 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight save_ctx = save_open_savefile(titlekey_type == TikTitleKeyType_Common ? TIK_COMMON_SAVEFILE_PATH : TIK_PERSONALIZED_SAVEFILE_PATH, 0); if (!save_ctx) { - LOGFILE("Failed to open ES %s ticket system savefile!", titlekey_type == TikTitleKeyType_Common ? "common" : "personalized"); + LOGFILE("Failed to open ES %s ticket system savefile!", g_tikTitleKeyTypeStrings[titlekey_type]); return false; } if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, TIK_SAVEFILE_STORAGE_PATH, &fat_storage, &ticket_bin_size)) { - LOGFILE("Failed to locate \"%s\" in ES %s ticket system save!", TIK_SAVEFILE_STORAGE_PATH, titlekey_type == TikTitleKeyType_Common ? "common" : "personalized"); + LOGFILE("Failed to locate \"%s\" in ES %s ticket system save!", TIK_SAVEFILE_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]); goto end; } if (ticket_bin_size < SIGNED_TIK_MIN_SIZE || (ticket_bin_size % SIGNED_TIK_MAX_SIZE) != 0) { - LOGFILE("Invalid size for \"%s\"! (0x%lX).", TIK_SAVEFILE_STORAGE_PATH, ticket_bin_size); + LOGFILE("Invalid size for \"%s\" in ES %s ticket system save! (0x%lX).", TIK_SAVEFILE_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type], ticket_bin_size); goto end; } - ticket_bin_buf = malloc(buf_size); - if (!ticket_bin_buf) + buf = malloc(buf_size); + if (!buf) { LOGFILE("Unable to allocate 0x%lX bytes block for temporary read buffer!", buf_size); goto end; } - while(total_br < ticket_bin_size) + if (!(ticket_entry = tikRetrieveTicketEntryFromTicketBin(&fat_storage, ticket_bin_size, buf, buf_size, id, titlekey_type))) { - if (buf_size > (ticket_bin_size - total_br)) buf_size = (ticket_bin_size - total_br); - - br = save_allocation_table_storage_read(&fat_storage, ticket_bin_buf, total_br, buf_size); - if (br != buf_size) - { - LOGFILE("Failed to read 0x%lX bytes chunk at offset 0x%lX from \"%s\" in ES %s ticket system save!", buf_size, total_br, TIK_SAVEFILE_STORAGE_PATH, \ - (titlekey_type == TikTitleKeyType_Common ? "common" : "personalized")); - goto end; - } - - total_br += br; - - for(i = 0; i < buf_size; i += SIGNED_TIK_MAX_SIZE) - { - if ((buf_size - i) < SIGNED_TIK_MIN_SIZE) break; - - TikCommonBlock *tik_common_block = tikGetCommonBlock(ticket_bin_buf + i); - if (tik_common_block && !memcmp(tik_common_block->rights_id.c, id->c, 0x10)) - { - /* Jackpot. */ - found_tik = true; - break; - } - } - - if (found_tik) break; - } - - if (!found_tik) - { - LOGFILE("Unable to find a matching ticket entry for the provided Rights ID!"); + LOGFILE("Unable to find a matching %s ticket entry for the provided Rights ID!", g_tikTitleKeyTypeStrings[titlekey_type]); goto end; } - if (!tikGetTicketTypeAndSize(ticket_bin_buf + i, SIGNED_TIK_MAX_SIZE, &(dst->type), &(dst->size))) + if (!tikGetTicketTypeAndSize(ticket_entry, SIGNED_TIK_MAX_SIZE, &(dst->type), &(dst->size))) { LOGFILE("Unable to determine ticket type and size!"); goto end; } - memcpy(dst->data, ticket_bin_buf + i, dst->size); + memcpy(dst->data, ticket_entry, dst->size); success = true; end: - if (ticket_bin_buf) free(ticket_bin_buf); + if (buf) free(buf); if (save_ctx) save_close_savefile(save_ctx); @@ -438,7 +446,7 @@ static bool tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out) if (!tikRetrieveRightsIdsByTitleKeyType(&rights_ids, &count, i == 1)) { - LOGFILE("Unable to retrieve %s rights IDs!", i == 0 ? "common" : "personalized"); + LOGFILE("Unable to retrieve %s rights IDs!", g_tikTitleKeyTypeStrings[i]); continue; } @@ -473,6 +481,7 @@ static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, Result rc = 0; u32 count = 0, ids_written = 0; FsRightsId *rights_ids = NULL; + u8 str_idx = (personalized ? TikTitleKeyType_Personalized : TikTitleKeyType_Common); *out = NULL; *out_count = 0; @@ -480,27 +489,27 @@ static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, rc = (personalized ? esCountPersonalizedTicket((s32*)&count) : esCountCommonTicket((s32*)&count)); if (R_FAILED(rc)) { - LOGFILE("esCount%sTicket failed! (0x%08X).", personalized ? "Personalized" : "Common", rc); + LOGFILE("esCount%c%sTicket failed! (0x%08X).", toupper(g_tikTitleKeyTypeStrings[str_idx][0]), g_tikTitleKeyTypeStrings[str_idx] + 1); return false; } if (!count) { - LOGFILE("No %s tickets available!", personalized ? "personalized" : "common"); + LOGFILE("No %s tickets available!", g_tikTitleKeyTypeStrings[str_idx]); return true; } rights_ids = calloc(count, sizeof(FsRightsId)); if (!rights_ids) { - LOGFILE("Unable to allocate memory for %s rights IDs!", personalized ? "personalized" : "common"); + LOGFILE("Unable to allocate memory for %s rights IDs!", g_tikTitleKeyTypeStrings[str_idx]); return false; } rc = (personalized ? esListPersonalizedTicket((s32*)&ids_written, rights_ids, (s32)count) : esListCommonTicket((s32*)&ids_written, rights_ids, (s32)count)); if (R_FAILED(rc) || ids_written != count) { - LOGFILE("esList%sTicket failed! (0x%08X). Wrote %u entries, expected %u entries.", personalized ? "Personalized" : "Common", rc, ids_written, count); + LOGFILE("esList%c%sTicket failed! (0x%08X). Wrote %u entries, expected %u entries.", toupper(g_tikTitleKeyTypeStrings[str_idx][0]), g_tikTitleKeyTypeStrings[str_idx] + 1, rc, ids_written, count); free(rights_ids); return false; } @@ -511,6 +520,106 @@ static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, return true; } +static u8 *tikRetrieveTicketEntryFromTicketBin(allocation_table_storage_ctx_t *fat_storage, u64 ticket_bin_size, u8 *buf, u64 buf_size, const FsRightsId *id, u8 titlekey_type) +{ + if (!fat_storage || ticket_bin_size < SIGNED_TIK_MIN_SIZE || (ticket_bin_size % SIGNED_TIK_MAX_SIZE) != 0 || !buf || !buf_size || (buf_size % SIGNED_TIK_MAX_SIZE) != 0 || !id) + { + LOGFILE("Invalid parameters!"); + return NULL; + } + + u64 br = 0, total_br = 0; + u8 *out_tik = NULL; + + Aes128CtrContext ctr_ctx = {0}; + u8 null_ctr[AES_BLOCK_SIZE] = {0}, ctr[AES_BLOCK_SIZE] = {0}, dec_tik[SIGNED_TIK_MAX_SIZE] = {0}; + + bool is_9x = hosversionAtLeast(9, 0, 0); + + if (is_9x && !memRetrieveFullProgramMemory(&g_esMemoryLocation)) + { + LOGFILE("Failed to retrieve ES program memory!"); + return NULL; + } + + while(total_br < ticket_bin_size) + { + if (buf_size > (ticket_bin_size - total_br)) buf_size = (ticket_bin_size - total_br); + + br = save_allocation_table_storage_read(fat_storage, buf, total_br, buf_size); + if (br != buf_size) + { + LOGFILE("Failed to read 0x%lX bytes chunk at offset 0x%lX from \"%s\" in ES %s ticket system save!", buf_size, total_br, TIK_SAVEFILE_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]); + break; + } + + for(u64 i = 0; i < buf_size; i += SIGNED_TIK_MAX_SIZE) + { + if ((buf_size - i) < SIGNED_TIK_MIN_SIZE) break; + + u8 *cur_tik = (buf + i); + u64 tik_offset = (total_br + i); + TikCommonBlock *tik_common_block = tikGetCommonBlock(cur_tik); + + if (!tik_common_block) + { + /* Check if we're dealing with a padding block. */ + if (!memcmp(cur_tik, null_ctr, sizeof(null_ctr))) continue; + + /* We're most likely dealing with an encrypted ticket. Don't proceed if HOS version isn't at least 9.0.0. */ + if (!is_9x) continue; + + /* Sad path. We need to retrieve the CTR key/IV from ES program memory in order to decrypt this ticket. */ + for(u64 j = 0; j < g_esMemoryLocation.data_size; j += ES_CTRKEY_ENTRY_ALIGNMENT) + { + if ((g_esMemoryLocation.data_size - j) < (sizeof(tikEsCtrKeyEntry9x) * 2)) break; + + /* Check if the key indexes are valid. idx2 should always be an odd number.*/ + tikEsCtrKeyPattern9x *pattern = (tikEsCtrKeyPattern9x*)(g_esMemoryLocation.data + j); + if (pattern->idx2 != (pattern->idx1 + 1) || !(pattern->idx2 & 1)) continue; + + /* Seems like indexes are valid. Check if the key is not null and if the CTR is. */ + tikEsCtrKeyEntry9x *key_entry = (tikEsCtrKeyEntry9x*)pattern; + if (!memcmp(key_entry->key, null_ctr, sizeof(null_ctr)) || memcmp(key_entry->ctr, null_ctr, sizeof(null_ctr)) != 0) continue; + + /* Check if we can decrypt the current ticket with this data. */ + memset(&ctr_ctx, 0, sizeof(Aes128CtrContext)); + aes128CtrInitializePartialCtr(ctr, key_entry->ctr, tik_offset); + aes128CtrContextCreate(&ctr_ctx, key_entry->key, ctr); + aes128CtrCrypt(&ctr_ctx, dec_tik, cur_tik, SIGNED_TIK_MAX_SIZE); + + if ((tik_common_block = tikGetCommonBlock(dec_tik)) != NULL && !strncmp(tik_common_block->issuer, "Root", 4)) + { + /* Ticket successfully decrypted. */ + memcpy(cur_tik, dec_tik, SIGNED_TIK_MAX_SIZE); + tik_common_block = tikGetCommonBlock(cur_tik); + break; + } + } + + /* Don't proceed if we couldn't decrypt the ticket. */ + if (!tik_common_block || strncmp(tik_common_block->issuer, "Root", 4) != 0) continue; + } + + /* Check if the rights ID from the ticket common block matches the one we're looking for. */ + if (!memcmp(tik_common_block->rights_id.c, id->c, 0x10)) + { + /* Jackpot. */ + out_tik = cur_tik; + break; + } + } + + total_br += br; + + if (out_tik) break; + } + + if (is_9x) memFreeMemoryLocation(&g_esMemoryLocation); + + return out_tik; +} + static bool tikGetTicketTypeAndSize(void *data, u64 data_size, u8 *out_type, u64 *out_size) { u32 sig_type = 0; diff --git a/source/tik.h b/source/tik.h index b05e00a..5b93e7e 100644 --- a/source/tik.h +++ b/source/tik.h @@ -55,8 +55,8 @@ typedef enum { TikPropertyMask_SharedTitle = BIT(1), TikPropertyMask_AllContents = BIT(2), TikPropertyMask_DeviceLinkIndepedent = BIT(3), - TikPropertyMask_Volatile = BIT(4), - TikPropertyMask_ELicenseRequired = BIT(5) + TikPropertyMask_Volatile = BIT(4), ///< Used to determine if the ticket copy inside ticket.bin should be encrypted or not. + TikPropertyMask_ELicenseRequired = BIT(5) ///< Used to determine if the console should connect to the Internet to perform elicense verification. } TikPropertyMask; /// Placed after the ticket signature block. @@ -167,6 +167,7 @@ typedef struct { u8 data[SIGNED_TIK_MAX_SIZE]; ///< Raw ticket data. u8 enc_titlekey[0x10]; ///< Titlekey with titlekek crypto (RSA-OAEP unwrapped if dealing with a TikTitleKeyType_Personalized ticket). u8 dec_titlekey[0x10]; ///< Titlekey without titlekek crypto. Ready to use for NCA FS section decryption. + char rights_id_str[0x21]; ///< Character string representation of the rights ID from the ticket. } Ticket; /// Retrieves a ticket from either the ES ticket system savedata file (eMMC BIS System partition) or the secure hash FS partition from an inserted gamecard, using a Rights ID value. diff --git a/source/title.c b/source/title.c index 74be69f..1406baa 100644 --- a/source/title.c +++ b/source/title.c @@ -1377,12 +1377,12 @@ static bool titleRetrieveContentMetaKeysFromDatabase(u8 storage_id) for(u32 j = 0; j < cur_title_info->content_count; j++) { titleConvertNcmContentSizeToU64(cur_title_info->content_infos[j].size, &tmp_size); - cur_title_info->title_size += tmp_size; + cur_title_info->size += tmp_size; } } /* Generate formatted title size string. */ - utilsGenerateFormattedSizeString(cur_title_info->title_size, cur_title_info->title_size_str, sizeof(cur_title_info->title_size_str)); + utilsGenerateFormattedSizeString(cur_title_info->size, cur_title_info->size_str, sizeof(cur_title_info->size_str)); } /* Update title info count. */ diff --git a/source/title.h b/source/title.h index 99b41ae..1521245 100644 --- a/source/title.h +++ b/source/title.h @@ -47,8 +47,8 @@ typedef struct _TitleInfo { VersionType1 version; ///< Holds the same value from meta_key.version. u32 content_count; ///< Content info count. NcmContentInfo *content_infos; ///< Content info entries from this title. - u64 title_size; ///< Total title size. - char title_size_str[32]; ///< Total title size string. + u64 size; ///< Total title size. + char size_str[32]; ///< Total title size string. TitleApplicationMetadata *app_metadata; ///< Only available for system titles and applications. struct _TitleInfo *parent, *previous, *next; ///< Used with TitleInfo entries from user applications, patches and add-on contents. The parent pointer is unused in user applications. } TitleInfo; @@ -222,4 +222,28 @@ NX_INLINE NcmContentInfo *titleGetContentInfoByTypeAndIdOffset(TitleInfo *info, return NULL; } +NX_INLINE u32 titleGetCountFromInfoBlock(TitleInfo *title_info) +{ + if (!title_info) return 0; + + u32 count = 1; + TitleInfo *cur_info = title_info->previous; + + while(cur_info) + { + count++; + cur_info = cur_info->previous; + } + + cur_info = title_info->next; + + while(cur_info) + { + count++; + cur_info = cur_info->next; + } + + return count; +} + #endif /* __TITLE_H__ */ diff --git a/source/usb.c b/source/usb.c index e93d3fd..538acca 100644 --- a/source/usb.c +++ b/source/usb.c @@ -672,7 +672,7 @@ static bool usbInitializeComms(void) goto end; } - if (hosversionAtLeast(5,0,0)) + if (hosversionAtLeast(5, 0, 0)) { u8 manufacturer = 0, product = 0, serial_number = 0; static const u16 supported_langs[1] = { 0x0409 }; @@ -805,7 +805,7 @@ static bool usbInitializeComms(void) goto end; } - if (hosversionAtLeast(5,0,0)) + if (hosversionAtLeast(5, 0, 0)) { rc = usbDsEnable(); if (R_FAILED(rc)) @@ -856,7 +856,7 @@ static void usbFreeDeviceInterface(void) NX_INLINE bool usbInitializeDeviceInterface(void) { - return (hosversionAtLeast(5,0,0) ? usbInitializeDeviceInterface5x() : usbInitializeDeviceInterface1x()); + return (hosversionAtLeast(5, 0, 0) ? usbInitializeDeviceInterface5x() : usbInitializeDeviceInterface1x()); } static bool usbInitializeDeviceInterface5x(void) diff --git a/source/utils.c b/source/utils.c index d2827f5..8d6d9e1 100644 --- a/source/utils.c +++ b/source/utils.c @@ -680,6 +680,28 @@ void utilsCreateDirectoryTree(const char *path, bool create_last_element) utilsCommitFileSystemChangesByPath(path); } +char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension) +{ + if (!prefix || !*prefix || !filename || !*filename || !extension || !*extension) + { + LOGFILE("Invalid parameters!"); + return NULL; + } + + char *path = NULL; + size_t path_len = (strlen(prefix) + strlen(filename) + strlen(extension) + 1); + + if (!(path = calloc(path_len, sizeof(char)))) + { + LOGFILE("Failed to allocate 0x%lX bytes for output path!", path_len); + return NULL; + } + + sprintf(path, "%s%s%s", prefix, filename, extension); + + return path; +} + bool utilsAppletModeCheck(void) { return (g_programAppletType != AppletType_Application && g_programAppletType != AppletType_SystemApplication); diff --git a/source/utils.h b/source/utils.h index 7f923f6..4e99631 100644 --- a/source/utils.h +++ b/source/utils.h @@ -115,6 +115,8 @@ bool utilsCreateConcatenationFile(const char *path); void utilsCreateDirectoryTree(const char *path, bool create_last_element); +char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension); + bool utilsAppletModeCheck(void); void utilsChangeHomeButtonBlockStatus(bool block); diff --git a/todo.txt b/todo.txt index 88beb92..cbd0ad8 100644 --- a/todo.txt +++ b/todo.txt @@ -11,24 +11,23 @@ list of top level functions designed to alter nca data in order of (possible) us * calls pfsGenerateEntryPatch * calls ncaGenerateHierarchicalSha256Patch * cnmtIsNcaPatchRequired -> not sure if i'll keep this - * missing wrapper for pfsWriteEntryPatchToMemoryBuffer + * missing wrapper for pfsWriteEntryPatchToMemoryBuffer !!! * programInfoChangeAcidPublicKeyAndNcaSignature (Program) - * calls npdmChangeAcidPublicKeyAndNcaSignature (Program) - * requires programInfoGenerateNcaPatch to be effective - * calls npdmGenerateNcaPatch (Program) - * calls pfsGenerateEntryPatch - * calls ncaGenerateHierarchicalSha256Patch - * programInfoIsNcaPatchRequired -> not sure if i'll keep this - * calls npdmIsNcaPatchRequired -> not sure if i'll keep this - * call inside programInfoChangeAcidPublicKeyAndNcaSignature maybe ??? - * missing wrapper for pfsWriteEntryPatchToMemoryBuffer + * calls npdmChangeAcidPublicKeyAndNcaSignature + * calls pfsGenerateEntryPatch + * calls ncaGenerateHierarchicalSha256Patch + * needs programInfoWriteNcaPatch to write patched data + * calls npdmWriteNcaPatch + * calls pfsWriteEntryPatchToMemoryBuffer + * calls ncaWriteHierarchicalSha256PatchToMemoryBuffer * nacpGenerateNcaPatch (Control) * calls romfsGenerateFileEntryPatch * calls ncaGenerateHierarchicalSha256Patch / ncaGenerateHierarchicalIntegrityPatch - * nacpIsNcaPatchRequired -> not sure if i'll keep this - * missing wrapper for romfsWriteFileEntryPatchToMemoryBuffer + * nacpIsNcaPatchRequired is used to check if a nacp patch was applied + * missing wrapper for romfsWriteFileEntryPatchToMemoryBuffer !!! + * missing functions for nacp mods !!! * ncaIsHeaderDirty (doesn't modify anything per se, but it's used to check if any of the functions above has been used, basically) * ncaEncryptHeader (doesn't modify anything per se, but it's used to generate new encrypted header data if needed) @@ -58,9 +57,11 @@ minor steps to take into account: todo: - nca: functions for fs section lookup? (could just let the user choose...) + nca: support for compressed fs sections? + nca: support for sparse sections? tik: option to wipe elicense property mask (otherwise, the console will attempt to connect to the Internet to perform elicense verification before launching the title the ticket belongs to) + tik: option to wipe volatile property mask (otherwise, the imported ticket will use an additional aes-ctr crypto layer in ticket.bin) tik: automatically dump tickets to the SD card? tik: use dumped tickets when the original ones can't be found in the ES savefile? @@ -73,6 +74,7 @@ todo: bktr: functions to display filelist (wrappers for romfs functions tbh) + title: fix titleinfo issue title: more functions for title lookup? (filters, patches / aoc, etc.) title: more functions for content lookup? (based on id) title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles