diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 798b30a..d008d6b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -20,6 +20,9 @@ Steps to reproduce the behavior: **Screenshots** Add screenshots to help explain your problem. +**Logfile** +If available, please upload your logfile located at `sdmc:/nxdumptool/nxdumptool.log`. + **Please fill the following information:** - Horizon OS (Switch FW) version: [e.g. 10.0.0] - CFW: [e.g. Atmosphère, SX OS, etc.] diff --git a/README.md b/README.md index 1ea5b12..3a01ec6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,14 @@ todo: - hfs0 methods + hfs0: filelist generation methods + + nca: more data replacement methods ??? + + pfs0: filelist generation methods pfs0: full header aligned to 0x20 (nsp) + + romfs: filelist generation methods + romfs: data replacement methods diff --git a/source/gamecard.c b/source/gamecard.c index 900f009..6b34c6a 100644 --- a/source/gamecard.c +++ b/source/gamecard.c @@ -104,27 +104,27 @@ static bool gamecardCreateDetectionThread(void); static void gamecardDestroyDetectionThread(void); static int gamecardDetectionThreadFunc(void *arg); -static inline bool gamecardIsInserted(void); +NX_INLINE bool gamecardIsInserted(void); static void gamecardLoadInfo(void); static void gamecardFreeInfo(void); static bool gamecardGetHandle(void); -static inline void gamecardCloseHandle(void); +NX_INLINE void gamecardCloseHandle(void); static bool gamecardOpenStorageArea(u8 area); static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool lock); static void gamecardCloseStorageArea(void); static bool gamecardGetStorageAreasSizes(void); -static inline u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size); +NX_INLINE u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size); static GameCardHashFileSystemHeader *gamecardGetHashFileSystemPartitionHeader(u8 hfs_partition_type, u32 *out_hfs_partition_idx); -static inline GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *header, u32 idx); -static inline char *gamecardGetHashFileSystemNameTable(void *header); -static inline char *gamecardGetHashFileSystemEntryNameByIndex(void *header, u32 idx); -static inline bool gamecardGetHashFileSystemEntryIndexByName(void *header, const char *name, u32 *out_idx); +NX_INLINE GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *header, u32 idx); +NX_INLINE char *gamecardGetHashFileSystemNameTable(void *header); +NX_INLINE char *gamecardGetHashFileSystemEntryNameByIndex(void *header, u32 idx); +NX_INLINE bool gamecardGetHashFileSystemEntryIndexByName(void *header, const char *name, u32 *out_idx); /* Service guard used to generate thread-safe initialize + exit functions. */ /* I'm using this here even though this actually isn't a real service but who cares, it gets the job done. */ @@ -570,7 +570,7 @@ static int gamecardDetectionThreadFunc(void *arg) return 0; } -static inline bool gamecardIsInserted(void) +NX_INLINE bool gamecardIsInserted(void) { bool inserted = false; Result rc = fsDeviceOperatorIsGameCardInserted(&g_deviceOperator, &inserted); @@ -695,7 +695,7 @@ static void gamecardLoadInfo(void) /* Calculate the full header size for the current hash FS partition and round it to a GAMECARD_MEDIA_UNIT_SIZE bytes boundary */ g_gameCardHfsPartitions[i].header_size = (sizeof(GameCardHashFileSystemHeader) + (partition_header.entry_count * sizeof(GameCardHashFileSystemEntry)) + partition_header.name_table_size); - g_gameCardHfsPartitions[i].header_size = ROUND_UP(g_gameCardHfsPartitions[i].header_size, GAMECARD_MEDIA_UNIT_SIZE); + g_gameCardHfsPartitions[i].header_size = ALIGN_UP(g_gameCardHfsPartitions[i].header_size, GAMECARD_MEDIA_UNIT_SIZE); /* Allocate memory for the hash FS partition header */ g_gameCardHfsPartitions[i].header = calloc(g_gameCardHfsPartitions[i].header_size, sizeof(u8)); @@ -796,7 +796,7 @@ static bool gamecardGetHandle(void) return true; } -static inline void gamecardCloseHandle(void) +NX_INLINE void gamecardCloseHandle(void) { svcCloseHandle(g_gameCardHandle.value); g_gameCardHandle.value = 0; @@ -894,8 +894,8 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool l success = true; } else { /* Fix offset and/or size to avoid unaligned reads */ - u64 block_start_offset = ROUND_DOWN(base_offset, GAMECARD_MEDIA_UNIT_SIZE); - u64 block_end_offset = ROUND_UP(base_offset + read_size, GAMECARD_MEDIA_UNIT_SIZE); + u64 block_start_offset = ALIGN_DOWN(base_offset, GAMECARD_MEDIA_UNIT_SIZE); + u64 block_end_offset = ALIGN_UP(base_offset + read_size, GAMECARD_MEDIA_UNIT_SIZE); u64 block_size = (block_end_offset - block_start_offset); u64 data_start_offset = (base_offset - block_start_offset); @@ -975,7 +975,7 @@ static bool gamecardGetStorageAreasSizes(void) return true; } -static inline u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size) +NX_INLINE u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size) { u64 capacity = 0; @@ -1027,20 +1027,20 @@ static GameCardHashFileSystemHeader *gamecardGetHashFileSystemPartitionHeader(u8 return fs_header; } -static inline GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *header, u32 idx) +NX_INLINE GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *header, u32 idx) { if (!header || idx >= ((GameCardHashFileSystemHeader*)header)->entry_count) return NULL; return (GameCardHashFileSystemEntry*)((u8*)header + sizeof(GameCardHashFileSystemHeader) + (idx * sizeof(GameCardHashFileSystemEntry))); } -static inline char *gamecardGetHashFileSystemNameTable(void *header) +NX_INLINE char *gamecardGetHashFileSystemNameTable(void *header) { GameCardHashFileSystemHeader *fs_header = (GameCardHashFileSystemHeader*)header; if (!fs_header || !fs_header->entry_count) return NULL; return ((char*)header + sizeof(GameCardHashFileSystemHeader) + (fs_header->entry_count * sizeof(GameCardHashFileSystemEntry))); } -static inline char *gamecardGetHashFileSystemEntryNameByIndex(void *header, u32 idx) +NX_INLINE char *gamecardGetHashFileSystemEntryNameByIndex(void *header, u32 idx) { GameCardHashFileSystemEntry *fs_entry = gamecardGetHashFileSystemEntryByIndex(header, idx); char *name_table = gamecardGetHashFileSystemNameTable(header); @@ -1048,17 +1048,17 @@ static inline char *gamecardGetHashFileSystemEntryNameByIndex(void *header, u32 return (name_table + fs_entry->name_offset); } -static inline bool gamecardGetHashFileSystemEntryIndexByName(void *header, const char *name, u32 *out_idx) +NX_INLINE bool gamecardGetHashFileSystemEntryIndexByName(void *header, const char *name, u32 *out_idx) { size_t name_len = 0; + GameCardHashFileSystemEntry *fs_entry = NULL; GameCardHashFileSystemHeader *fs_header = (GameCardHashFileSystemHeader*)header; char *name_table = gamecardGetHashFileSystemNameTable(header); if (!fs_header || !fs_header->entry_count || !name_table || !name || !(name_len = strlen(name)) || !out_idx) return false; for(u32 i = 0; i < fs_header->entry_count; i++) { - GameCardHashFileSystemEntry *fs_entry = gamecardGetHashFileSystemEntryByIndex(header, i); - if (!fs_entry) continue; + if (!(fs_entry = gamecardGetHashFileSystemEntryByIndex(header, i))) return false; if (!strncmp(name_table + fs_entry->name_offset, name, name_len)) { diff --git a/source/main.c b/source/main.c index 7d646f8..fa83e39 100644 --- a/source/main.c +++ b/source/main.c @@ -26,7 +26,8 @@ #include #include "nca.h" -#include "pfs0.h" +#include "pfs.h" +#include "rsa.h" @@ -91,8 +92,9 @@ int main(int argc, char *argv[]) } }; - PartitionFileSystemContext pfs0_ctx = {0}; - PartitionFileSystemEntry *pfs0_entry = NULL; + u64 pfs_size = 0; + PartitionFileSystemContext pfs_ctx = {0}; + PartitionFileSystemEntry *pfs_entry = NULL; buf = malloc(0x400000); if (!buf) @@ -175,67 +177,66 @@ int main(int argc, char *argv[]) consoleUpdate(NULL); - if (!pfs0InitializeContext(&pfs0_ctx, &(nca_ctx->fs_contexts[0]))) + if (!pfsInitializeContext(&pfs_ctx, &(nca_ctx->fs_contexts[0]))) { - printf("pfs0 initialize ctx failed\n"); + printf("pfs initialize ctx failed\n"); goto out2; } - printf("pfs0 initialize ctx succeeded\n"); + printf("pfs initialize ctx succeeded\n"); consoleUpdate(NULL); - tmp_file = fopen("sdmc:/nxdt_test/pfs0_ctx.bin", "wb"); + if (pfsGetTotalDataSize(&pfs_ctx, &pfs_size)) + { + printf("pfs size succeeded: 0x%lX\n", pfs_size); + } else { + printf("pfs size failed\n"); + } + + consoleUpdate(NULL); + + tmp_file = fopen("sdmc:/nxdt_test/pfs_ctx.bin", "wb"); if (tmp_file) { - fwrite(&pfs0_ctx, 1, sizeof(PartitionFileSystemContext), tmp_file); + fwrite(&pfs_ctx, 1, sizeof(PartitionFileSystemContext), tmp_file); fclose(tmp_file); tmp_file = NULL; - printf("pfs0 ctx saved\n"); + printf("pfs ctx saved\n"); } else { - printf("pfs0 ctx not saved\n"); + printf("pfs ctx not saved\n"); } consoleUpdate(NULL); - tmp_file = fopen("sdmc:/nxdt_test/pfs0_header.bin", "wb"); + tmp_file = fopen("sdmc:/nxdt_test/pfs_header.bin", "wb"); if (tmp_file) { - fwrite(pfs0_ctx.header, 1, pfs0_ctx.header_size, tmp_file); + fwrite(pfs_ctx.header, 1, pfs_ctx.header_size, tmp_file); fclose(tmp_file); tmp_file = NULL; - printf("pfs0 header saved\n"); + printf("pfs header saved\n"); } else { - printf("pfs0 header not saved\n"); + printf("pfs header not saved\n"); } consoleUpdate(NULL); - pfs0_entry = pfs0GetEntryByName(&pfs0_ctx, "main.npdm"); - if (!pfs0_entry) - { - printf("pfs0 get entry by name failed\n"); - goto out2; - } - - printf("pfs0 get entry by name succeeded\n"); - consoleUpdate(NULL); - - tmp_file = fopen("sdmc:/nxdt_test/main.npdm", "wb"); + tmp_file = fopen("sdmc:/nxdt_test/pfs.bin", "wb"); if (tmp_file) { u64 blksize = 0x400000; - u64 total = pfs0_entry->size; + u64 total = pfs_ctx.size; - printf("main.npdm created. Target size -> 0x%lX\n", total); + printf("pfs created: 0x%lX\n", total); consoleUpdate(NULL); for(u64 curpos = 0; curpos < total; curpos += blksize) { if (blksize > (total - curpos)) blksize = (total - curpos); - if (!pfs0ReadEntryData(&pfs0_ctx, pfs0_entry, buf, blksize, 0)) + if (!pfsReadPartitionData(&pfs_ctx, buf, blksize, curpos)) { - printf("pfs0 read entry data failed\n"); + printf("pfs read partition failed\n"); goto out2; } @@ -245,11 +246,110 @@ int main(int argc, char *argv[]) fclose(tmp_file); tmp_file = NULL; - printf("pfs0 read main.npdm success\n"); + printf("pfs read partition success\n"); + } else { + printf("pfs not created\n"); + } + + consoleUpdate(NULL); + + pfs_entry = pfsGetEntryByName(&pfs_ctx, "main.npdm"); + if (!pfs_entry) + { + printf("pfs get entry by name failed\n"); + goto out2; + } + + printf("pfs get entry by name succeeded\n"); + consoleUpdate(NULL); + + tmp_file = fopen("sdmc:/nxdt_test/main.npdm", "wb"); + if (tmp_file) + { + printf("main.npdm created. Target size -> 0x%lX\n", pfs_entry->size); + consoleUpdate(NULL); + + if (!pfsReadEntryData(&pfs_ctx, pfs_entry, buf, pfs_entry->size, 0)) + { + printf("pfs read entry data failed\n"); + goto out2; + } + + fwrite(buf, 1, pfs_entry->size, tmp_file); + fclose(tmp_file); + tmp_file = NULL; + + printf("pfs read main.npdm success\n"); } else { printf("main.npdm not created\n"); } + consoleUpdate(NULL); + + u32 acid_offset = 0; + memcpy(&acid_offset, buf + 0x78, sizeof(u32)); + + PartitionFileSystemModifiedBlockInfo pfs_block_info = {0}; + + if (pfsGenerateModifiedEntryData(&pfs_ctx, pfs_entry, rsa2048GetCustomAcidPublicKey(), RSA2048_SIGNATURE_SIZE, acid_offset + RSA2048_SIGNATURE_SIZE, &pfs_block_info)) + { + printf("pfs mod data success | hbo: 0x%lX | hbs: 0x%lX | dbo: 0x%lX | dbs: 0x%lX\n", pfs_block_info.hash_block_offset, pfs_block_info.hash_block_size, pfs_block_info.data_block_offset, pfs_block_info.data_block_size); + + consoleUpdate(NULL); + + tmp_file = fopen("sdmc:/nxdt_test/pfs_mod.bin", "wb"); + if (tmp_file) + { + fwrite(&pfs_block_info, 1, sizeof(PartitionFileSystemModifiedBlockInfo), tmp_file); + fclose(tmp_file); + tmp_file = NULL; + printf("pfs mod data saved\n"); + } else { + printf("pfs mod data not saved\n"); + } + + consoleUpdate(NULL); + + tmp_file = fopen("sdmc:/nxdt_test/pfs_hash_mod.bin", "wb"); + if (tmp_file) + { + fwrite(pfs_block_info.hash_block, 1, pfs_block_info.hash_block_size, tmp_file); + fclose(tmp_file); + tmp_file = NULL; + printf("pfs hash mod data saved\n"); + } else { + printf("pfs hash mod data not saved\n"); + } + + consoleUpdate(NULL); + + tmp_file = fopen("sdmc:/nxdt_test/pfs_data_mod.bin", "wb"); + if (tmp_file) + { + fwrite(pfs_block_info.data_block, 1, pfs_block_info.data_block_size, tmp_file); + fclose(tmp_file); + tmp_file = NULL; + printf("pfs data mod data saved\n"); + } else { + printf("pfs data mod data not saved\n"); + } + + consoleUpdate(NULL); + + tmp_file = fopen("sdmc:/nxdt_test/new_nca_ctx.bin", "wb"); + if (tmp_file) + { + fwrite(nca_ctx, 1, sizeof(NcaContext), tmp_file); + fclose(tmp_file); + tmp_file = NULL; + printf("nca ctx saved\n"); + } else { + printf("nca ctx not saved\n"); + } + } else { + printf("pfs mod data failed\n"); + } + out2: while(appletMainLoop()) { @@ -260,7 +360,7 @@ out2: if (tmp_file) fclose(tmp_file); - pfs0FreeContext(&pfs0_ctx); + pfsFreeContext(&pfs_ctx); if (serviceIsActive(&(ncm_storage.s))) ncmContentStorageClose(&ncm_storage); diff --git a/source/nca.c b/source/nca.c index bf928dd..17e74a1 100644 --- a/source/nca.c +++ b/source/nca.c @@ -43,13 +43,13 @@ static size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const voi static bool ncaDecryptHeader(NcaContext *ctx); static bool ncaDecryptKeyArea(NcaContext *ctx); -static inline bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx); -static inline u8 ncaGetKeyGenerationValue(NcaContext *ctx); -static inline bool ncaCheckRightsIdAvailability(NcaContext *ctx); +NX_INLINE bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx); +NX_INLINE u8 ncaGetKeyGenerationValue(NcaContext *ctx); +NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx); -static inline void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset); -static inline void ncaUpdateAesCtrIv(u8 *ctr, u64 offset); -static inline void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset); +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); @@ -298,11 +298,11 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, NcmContentStorage *ncm { switch(out->fs_contexts[i].section_num) { - case 0: /* ExeFS PFS0 */ + case 0: /* ExeFS Partition FS */ case 1: /* RomFS */ out->fs_contexts[i].encryption_type = NcaEncryptionType_AesCtr; break; - case 2: /* Logo PFS0 */ + case 2: /* Logo Partition FS */ out->fs_contexts[i].encryption_type = NcaEncryptionType_None; break; default: @@ -392,7 +392,7 @@ bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 of return _ncaReadFsSection(ctx, out, read_size, offset, true); } -void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset) +void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset) { mutexLock(&g_ncaCryptoBufferMutex); @@ -466,8 +466,8 @@ void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, void *data, u } /* Calculate block offsets and size */ - block_start_offset = ROUND_DOWN(data_offset, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE); - block_end_offset = ROUND_UP(data_offset + data_size, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE); + block_start_offset = ALIGN_DOWN(data_offset, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE); + block_end_offset = ALIGN_UP(data_offset + data_size, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE); block_size = (block_end_offset - block_start_offset); plain_chunk_offset = (data_offset - block_start_offset); @@ -698,25 +698,24 @@ static bool ncaDecryptKeyArea(NcaContext *ctx) return true; } -static inline bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx) +NX_INLINE bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx) { if (!ctx || ctx->format_version != NcaVersion_Nca0) return false; u8 nca0_key_area_hash[SHA256_HASH_SIZE] = {0}; sha256CalculateHash(nca0_key_area_hash, ctx->header.encrypted_keys, 0x40); - if (!memcmp(nca0_key_area_hash, g_nca0KeyAreaHash, SHA256_HASH_SIZE)) return false; return true; } -static inline u8 ncaGetKeyGenerationValue(NcaContext *ctx) +NX_INLINE u8 ncaGetKeyGenerationValue(NcaContext *ctx) { if (!ctx) return 0; return (ctx->header.key_generation > ctx->header.key_generation_old ? ctx->header.key_generation : ctx->header.key_generation_old); } -static inline bool ncaCheckRightsIdAvailability(NcaContext *ctx) +NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx) { if (!ctx) return false; @@ -734,7 +733,7 @@ static inline bool ncaCheckRightsIdAvailability(NcaContext *ctx) return rights_id_available; } -static inline void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset) +NX_INLINE void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset) { if (!out || !ctr) return; @@ -748,7 +747,7 @@ static inline void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset) } } -static inline void ncaUpdateAesCtrIv(u8 *ctr, u64 offset) +NX_INLINE void ncaUpdateAesCtrIv(u8 *ctr, u64 offset) { if (!ctr) return; @@ -761,7 +760,7 @@ static inline void ncaUpdateAesCtrIv(u8 *ctr, u64 offset) } } -static inline void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset) +NX_INLINE void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset) { if (!ctr) return; @@ -853,8 +852,8 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size } /* Calculate offsets and block sizes */ - block_start_offset = ROUND_DOWN(content_offset, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE); - block_end_offset = ROUND_UP(content_offset + read_size, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE); + block_start_offset = ALIGN_DOWN(content_offset, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE); + block_end_offset = ALIGN_UP(content_offset + read_size, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE); block_size = (block_end_offset - block_start_offset); data_start_offset = (content_offset - block_start_offset); diff --git a/source/nca.h b/source/nca.h index f7988cb..8519d8c 100644 --- a/source/nca.h +++ b/source/nca.h @@ -31,7 +31,11 @@ #define NCA_NCA2_MAGIC 0x4E434132 /* "NCA2" */ #define NCA_NCA3_MAGIC 0x4E434133 /* "NCA3" */ +#define NCA_HIERARCHICAL_SHA256_LAYER_COUNT 2 + #define NCA_IVFC_MAGIC 0x49564643 /* "IVFC" */ +#define NCA_IVFC_HASH_DATA_LAYER_COUNT 5 +#define NCA_IVFC_BLOCK_SIZE(x) (1 << (x)) #define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR" */ @@ -41,8 +45,6 @@ #define NCA_AES_XTS_SECTOR_SIZE 0x200 #define NCA_NCA0_FS_HEADER_AES_XTS_SECTOR(x) (((x) - NCA_HEADER_LENGTH) >> 9) -#define NCA_IVFC_BLOCK_SIZE(x) (1 << (x)) - typedef enum { NcaDistributionType_Download = 0, NcaDistributionType_GameCard = 1 @@ -141,11 +143,11 @@ typedef struct { /// Used for NcaFsType_RomFs. typedef struct { - u32 magic; ///< "IVFC". + u32 magic; ///< "IVFC". u32 version; u32 master_hash_size; u32 layer_count; - NcaHierarchicalIntegrityLayerInfo hash_data_layer_info[5]; + NcaHierarchicalIntegrityLayerInfo hash_data_layer_info[NCA_IVFC_HASH_DATA_LAYER_COUNT]; NcaHierarchicalIntegrityLayerInfo hash_target_layer_info; u8 signature_salt[0x20]; u8 master_hash[0x20]; @@ -310,7 +312,7 @@ bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 of /// Input offset must be relative to the start of the NCA FS section. /// Output size and offset are guaranteed to be aligned to the AES sector size used by the encryption type from the FS section. /// Output offset is relative to the start of the NCA content file, making it easier to use the output encrypted block to replace data in-place while writing a NCA. -void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset); +void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset); @@ -325,30 +327,59 @@ bool ncaEncryptHeader(NcaContext *ctx); /// Miscellanous functions. -static inline void ncaConvertNcmContentSizeToU64(const u8 *size, u64 *out) +NX_INLINE void ncaConvertNcmContentSizeToU64(const u8 *size, u64 *out) { if (!size || !out) return; *out = 0; memcpy(out, size, 6); } -static inline void ncaConvertU64ToNcmContentSize(const u64 *size, u8 *out) +NX_INLINE void ncaConvertU64ToNcmContentSize(const u64 *size, u8 *out) { if (size && out) memcpy(out, size, 6); } -static inline void ncaSetDownloadDistributionType(NcaContext *ctx) +NX_INLINE void ncaSetDownloadDistributionType(NcaContext *ctx) { if (!ctx || ctx->header.distribution_type == NcaDistributionType_Download) return; ctx->header.distribution_type = NcaDistributionType_Download; ctx->dirty_header = true; } -static inline void ncaWipeRightsId(NcaContext *ctx) +NX_INLINE void ncaWipeRightsId(NcaContext *ctx) { if (!ctx || !ctx->rights_id_available) return; memset(&(ctx->header.rights_id), 0, sizeof(FsRightsId)); ctx->dirty_header = true; } +NX_INLINE bool ncaValidateHierarchicalSha256Offsets(NcaHierarchicalSha256 *hierarchical_sha256, u64 section_size) +{ + if (!hierarchical_sha256 || !section_size || !hierarchical_sha256->hash_block_size || hierarchical_sha256->layer_count != NCA_HIERARCHICAL_SHA256_LAYER_COUNT) return false; + + /* Validate layer offsets and sizes */ + for(u8 i = 0; i < NCA_HIERARCHICAL_SHA256_LAYER_COUNT; i++) + { + NcaHierarchicalSha256LayerInfo *layer_info = (i == 0 ? &(hierarchical_sha256->hash_data_layer_info) : &(hierarchical_sha256->hash_target_layer_info)); + if (layer_info->offset >= section_size || !layer_info->size || (layer_info->offset + layer_info->size) > section_size) return false; + } + + return true; +} + +NX_INLINE bool ncaValidateHierarchicalIntegrityOffsets(NcaHierarchicalIntegrity *hierarchical_integrity, u64 section_size) +{ + if (!hierarchical_integrity || !section_size || __builtin_bswap32(hierarchical_integrity->magic) != NCA_IVFC_MAGIC || !hierarchical_integrity->master_hash_size || \ + hierarchical_integrity->layer_count != NCA_IVFC_HASH_DATA_LAYER_COUNT) return false; + + /* Validate layer offsets and sizes */ + for(u8 i = 0; i < (NCA_IVFC_HASH_DATA_LAYER_COUNT + 1); i++) + { + NcaHierarchicalIntegrityLayerInfo *layer_info = (i < NCA_IVFC_HASH_DATA_LAYER_COUNT ? &(hierarchical_integrity->hash_data_layer_info[i]) : &(hierarchical_integrity->hash_target_layer_info)); + if (layer_info->offset >= section_size || !layer_info->size || !layer_info->block_size || (layer_info->offset + layer_info->size) > section_size) return false; + } + + return true; +} + #endif /* __NCA_H__ */ diff --git a/source/pfs.c b/source/pfs.c new file mode 100644 index 0000000..3cadd42 --- /dev/null +++ b/source/pfs.c @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2020 DarkMatterCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "pfs.h" +#include "utils.h" + +bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx) +{ + if (!out || !nca_fs_ctx || nca_fs_ctx->section_type != NcaFsSectionType_PartitionFs || !nca_fs_ctx->header || nca_fs_ctx->header->fs_type != NcaFsType_PartitionFs || \ + nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalSha256) + { + LOGFILE("Invalid parameters!"); + return false; + } + + /* Fill context */ + out->nca_fs_ctx = nca_fs_ctx; + out->hash_info = &(nca_fs_ctx->header->hash_info.hierarchical_sha256); + out->offset = 0; + out->size = 0; + out->is_exefs = false; + out->header_size = 0; + out->header = NULL; + + if (!ncaValidateHierarchicalSha256Offsets(out->hash_info, nca_fs_ctx->section_size)) + { + LOGFILE("Invalid HierarchicalSha256 block!"); + return false; + } + + out->offset = out->hash_info->hash_target_layer_info.offset; + out->size = out->hash_info->hash_target_layer_info.size; + + /* Read partial PFS header */ + u32 magic = 0; + PartitionFileSystemHeader pfs_header = {0}; + PartitionFileSystemEntry *main_npdm_entry = NULL; + + if (!ncaReadFsSection(nca_fs_ctx, &pfs_header, sizeof(PartitionFileSystemHeader), out->offset)) + { + LOGFILE("Failed to read partial partition FS header!"); + return false; + } + + magic = __builtin_bswap32(pfs_header.magic); + if (magic != PFS0_MAGIC) + { + LOGFILE("Invalid partition FS magic word! (0x%08X)", magic); + return false; + } + + if (!pfs_header.entry_count || !pfs_header.name_table_size) + { + LOGFILE("Invalid partition FS entry count / name table size!"); + return false; + } + + /* Calculate full partition FS header size */ + out->header_size = (sizeof(PartitionFileSystemHeader) + (pfs_header.entry_count * sizeof(PartitionFileSystemEntry)) + pfs_header.name_table_size); + + /* Allocate memory for the full partition FS header */ + out->header = calloc(out->header_size, sizeof(u8)); + if (!out->header) + { + LOGFILE("Unable to allocate 0x%lX bytes buffer for the full partition FS header!", out->header_size); + return false; + } + + /* Read full partition FS header */ + if (!ncaReadFsSection(nca_fs_ctx, out->header, out->header_size, out->offset)) + { + LOGFILE("Failed to read full partition FS header!"); + return false; + } + + /* Check if we're dealing with an ExeFS section */ + if ((main_npdm_entry = pfsGetEntryByName(out, "main.npdm")) != NULL && pfsReadEntryData(out, main_npdm_entry, &magic, sizeof(u32), 0) && \ + __builtin_bswap32(magic) == NPDM_META_MAGIC) out->is_exefs = true; + + return true; +} + +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) + { + LOGFILE("Invalid parameters!"); + return false; + } + + /* Read partition data */ + if (!ncaReadFsSection(ctx->nca_fs_ctx, out, read_size, ctx->offset + offset)) + { + LOGFILE("Failed to read partition FS data!"); + return false; + } + + return true; +} + +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) + { + LOGFILE("Invalid parameters!"); + return false; + } + + /* Read entry data */ + if (!pfsReadPartitionData(ctx, out, read_size, ctx->header_size + fs_entry->offset + offset)) + { + LOGFILE("Failed to read partition FS entry data!"); + return false; + } + + return true; +} + +bool pfsGenerateModifiedEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, PartitionFileSystemModifiedBlockInfo *out) +{ + NcaContext *nca_ctx = NULL; + + if (!ctx || !ctx->nca_fs_ctx || !(nca_ctx = (NcaContext*)ctx->nca_fs_ctx->nca_ctx) || !ctx->hash_info || !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) + { + LOGFILE("Invalid parameters!"); + return false; + } + + /* Calculate required offsets and sizes */ + u64 partition_offset = (ctx->header_size + fs_entry->offset + data_offset); + u64 block_size = ctx->hash_info->hash_block_size; + + u64 hash_table_offset = ctx->hash_info->hash_data_layer_info.offset; + u64 hash_table_size = ctx->hash_info->hash_data_layer_info.size; + + u64 hash_block_start_offset = ((partition_offset / block_size) * SHA256_HASH_SIZE); + u64 hash_block_end_offset = (((partition_offset + data_size) / block_size) * SHA256_HASH_SIZE); + u64 hash_block_size = (hash_block_end_offset != hash_block_start_offset ? (hash_block_end_offset - hash_block_start_offset) : SHA256_HASH_SIZE); + + u64 data_block_start_offset = (ctx->offset + ALIGN_DOWN(partition_offset, block_size)); + u64 data_block_end_offset = (ctx->offset + ALIGN_UP(partition_offset + data_size, block_size)); + u64 data_block_size = (data_block_end_offset - data_block_start_offset); + + u64 block_count = (hash_block_size / SHA256_HASH_SIZE); + u64 new_data_offset = (partition_offset - ALIGN_DOWN(partition_offset, block_size)); + + u8 *hash_table = NULL, *data_block = NULL; + + bool success = false; + + /* Allocate memory for the full hash table */ + hash_table = malloc(hash_table_size); + if (!hash_table) + { + LOGFILE("Unable to allocate 0x%lX bytes buffer for the full partition FS hash table!", hash_table_size); + goto exit; + } + + /* Read full hash table */ + if (!ncaReadFsSection(ctx->nca_fs_ctx, hash_table, hash_table_size, hash_table_offset)) + { + LOGFILE("Failed to read full partition FS hash table!"); + goto exit; + } + + /* Allocate memory for the modified data block */ + data_block = malloc(data_block_size); + if (!data_block) + { + LOGFILE("Unable to allocate 0x%lX bytes buffer for the modified partition FS data block!", data_block_size); + goto exit; + } + + /* Read data block */ + if (!ncaReadFsSection(ctx->nca_fs_ctx, data_block, data_block_size, data_block_start_offset)) + { + LOGFILE("Failed to read partition FS data block!"); + goto exit; + } + + /* Replace data */ + memcpy(data_block + new_data_offset, data, data_size); + + /* Recalculate hashes */ + for(u64 i = 0; i < block_count; i++) sha256CalculateHash(hash_table + hash_block_start_offset + (i * SHA256_HASH_SIZE), data_block + (i * block_size), block_size); + + /* Reencrypt hash block */ + out->hash_block = ncaGenerateEncryptedFsSectionBlock(ctx->nca_fs_ctx, hash_table + hash_block_start_offset, hash_block_size, hash_table_offset + hash_block_start_offset, \ + &(out->hash_block_size), &(out->hash_block_offset)); + if (!out->hash_block) + { + LOGFILE("Failed to generate encrypted partition FS hash block!"); + goto exit; + } + + /* Reencrypt data block */ + out->data_block = ncaGenerateEncryptedFsSectionBlock(ctx->nca_fs_ctx, data_block, data_block_size, data_block_start_offset, &(out->data_block_size), &(out->data_block_offset)); + if (!out->data_block) + { + LOGFILE("Failed to generate encrypted partition FS data block!"); + goto exit; + } + + /* Recalculate master hash from hash info block */ + sha256CalculateHash(ctx->hash_info->master_hash, hash_table, hash_table_size); + + /* Recalculate FS header hash */ + sha256CalculateHash(nca_ctx->header.fs_hashes[ctx->nca_fs_ctx->section_num].hash, ctx->header, sizeof(NcaFsHeader)); + + /* Enable the 'dirty_header' flag */ + nca_ctx->dirty_header = true; + + success = true; + +exit: + if (data_block) free(data_block); + + if (hash_table) free(hash_table); + + return success; +} diff --git a/source/pfs.h b/source/pfs.h new file mode 100644 index 0000000..f6a93de --- /dev/null +++ b/source/pfs.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2020 DarkMatterCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#ifndef __PFS_H__ +#define __PFS_H__ + +#include +#include "nca.h" + +#define PFS0_MAGIC 0x50465330 /* "PFS0" */ + +typedef struct { + u32 magic; ///< "PFS0". + u32 entry_count; + u32 name_table_size; + u8 reserved[0x4]; +} PartitionFileSystemHeader; + +typedef struct { + u64 offset; + u64 size; + u32 name_offset; + u8 reserved[0x4]; +} PartitionFileSystemEntry; + +typedef struct { + NcaFsSectionContext *nca_fs_ctx; ///< Used to read NCA FS section data. + NcaHierarchicalSha256 *hash_info; ///< Hash table information. + u64 offset; ///< Partition offset (relative to the start of the NCA FS section). + u64 size; ///< Partition size. + bool is_exefs; ///< ExeFS flag. + u64 header_size; ///< Full header size. + u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table. +} PartitionFileSystemContext; + +typedef struct { + u64 hash_block_offset; ///< New hash block offset (relative to the start of the NCA content file). + u64 hash_block_size; ///< New hash block size. + u8 *hash_block; ///< New hash block contents. + u64 data_block_offset; ///< New data block offset. + u64 data_block_size; ///< New data block size. + u8 *data_block; ///< New data block contents. +} PartitionFileSystemModifiedBlockInfo; + +/// Initializes a partition FS context. +bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx); + +/// Cleanups a previously initialized partition FS context. +NX_INLINE void pfsFreeContext(PartitionFileSystemContext *ctx) +{ + if (!ctx) return; + if (ctx->header) free(ctx->header); + memset(ctx, 0, sizeof(PartitionFileSystemContext)); +} + +/// Reads raw partition data using a partition FS context. +bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_size, u64 offset); + +/// Reads data from a previously retrieved PartitionFileSystemEntry using a partition FS context. +bool pfsReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, void *out, u64 read_size, u64 offset); + +/// Generates modified + encrypted hash and data blocks using a partition FS context + entry information. Both blocks are ready to be used to replace NCA content data during writing operations. +/// Input offset must be relative to the start of the partition FS entry data. +/// Bear in mind that this function recalculates both the NcaHashInfo block master hash and the NCA FS header hash from the NCA header, and enables the 'dirty_header' flag from the NCA context. +/// As such, this function is only capable of modifying a single file from a partition FS in a NCA content file. +bool pfsGenerateModifiedEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, PartitionFileSystemModifiedBlockInfo *out); + +/// Miscellaneous functions. + +NX_INLINE u32 pfsGetEntryCount(PartitionFileSystemContext *ctx) +{ + if (!ctx || !ctx->header_size || !ctx->header) return 0; + return ((PartitionFileSystemHeader*)ctx->header)->entry_count; +} + +NX_INLINE PartitionFileSystemEntry *pfsGetEntryByIndex(PartitionFileSystemContext *ctx, u32 idx) +{ + if (idx >= pfsGetEntryCount(ctx)) return NULL; + return (PartitionFileSystemEntry*)(ctx->header + sizeof(PartitionFileSystemHeader) + (idx * sizeof(PartitionFileSystemEntry))); +} + +NX_INLINE char *pfsGetNameTable(PartitionFileSystemContext *ctx) +{ + u32 entry_count = pfsGetEntryCount(ctx); + if (!entry_count) return NULL; + return (char*)(ctx->header + sizeof(PartitionFileSystemHeader) + (entry_count * sizeof(PartitionFileSystemEntry))); +} + +NX_INLINE char *pfsGetEntryNameByIndex(PartitionFileSystemContext *ctx, u32 idx) +{ + PartitionFileSystemEntry *fs_entry = pfsGetEntryByIndex(ctx, idx); + char *name_table = pfsGetNameTable(ctx); + if (!fs_entry || !name_table) return NULL; + return (name_table + fs_entry->name_offset); +} + +NX_INLINE bool pfsGetEntryIndexByName(PartitionFileSystemContext *ctx, const char *name, u32 *out_idx) +{ + size_t name_len = 0; + PartitionFileSystemEntry *fs_entry = NULL; + u32 entry_count = pfsGetEntryCount(ctx); + char *name_table = pfsGetNameTable(ctx); + if (!entry_count || !name_table || !name || !(name_len = strlen(name)) || !out_idx) return false; + + for(u32 i = 0; i < entry_count; i++) + { + if (!(fs_entry = pfsGetEntryByIndex(ctx, i))) return false; + + if (!strncmp(name_table + fs_entry->name_offset, name, name_len)) + { + *out_idx = i; + return true; + } + } + + return false; +} + +NX_INLINE PartitionFileSystemEntry *pfsGetEntryByName(PartitionFileSystemContext *ctx, const char *name) +{ + u32 idx = 0; + if (!pfsGetEntryIndexByName(ctx, name, &idx)) return NULL; + return pfsGetEntryByIndex(ctx, idx); +} + +NX_INLINE bool pfsGetTotalDataSize(PartitionFileSystemContext *ctx, u64 *out_size) +{ + u64 total_size = 0; + u32 entry_count = pfsGetEntryCount(ctx); + PartitionFileSystemEntry *fs_entry = NULL; + if (!entry_count || !out_size) return false; + + for(u32 i = 0; i < entry_count; i++) + { + if (!(fs_entry = pfsGetEntryByIndex(ctx, i))) return false; + total_size += fs_entry->size; + } + + *out_size = total_size; + + return true; +} + +#endif /* __PFS_H__ */ diff --git a/source/pfs0.c b/source/pfs0.c deleted file mode 100644 index d4efc2b..0000000 --- a/source/pfs0.c +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2020 DarkMatterCore - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include - -#include "pfs0.h" -#include "utils.h" - -#define PFS0_NCA_FS_HEADER_LAYER_COUNT 2 - -#define NPDM_META_MAGIC 0x4D455441 /* "META" */ - -bool pfs0InitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx) -{ - if (!out || !nca_fs_ctx || nca_fs_ctx->section_type != NcaFsSectionType_PartitionFs || !nca_fs_ctx->header || nca_fs_ctx->header->fs_type != NcaFsType_PartitionFs || \ - nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalSha256) - { - LOGFILE("Invalid parameters!"); - return false; - } - - /* Fill context */ - out->nca_fs_ctx = nca_fs_ctx; - out->hash_info = &(nca_fs_ctx->header->hash_info.hierarchical_sha256); - out->offset = 0; - out->size = 0; - out->is_exefs = false; - out->header_size = 0; - out->header = NULL; - - if (!out->hash_info->hash_block_size || out->hash_info->layer_count != PFS0_NCA_FS_HEADER_LAYER_COUNT || out->hash_info->hash_data_layer_info.offset >= out->nca_fs_ctx->section_size || \ - !out->hash_info->hash_data_layer_info.size || (out->hash_info->hash_data_layer_info.offset + out->hash_info->hash_data_layer_info.size) > out->nca_fs_ctx->section_size || \ - out->hash_info->hash_target_layer_info.offset >= out->nca_fs_ctx->section_size || !out->hash_info->hash_target_layer_info.size || \ - (out->hash_info->hash_target_layer_info.offset + out->hash_info->hash_target_layer_info.size) > out->nca_fs_ctx->section_size) - { - LOGFILE("Invalid HierarchicalSha256 block!"); - return false; - } - - out->offset = out->hash_info->hash_target_layer_info.offset; - out->size = out->hash_info->hash_target_layer_info.size; - - /* Read partial PFS0 header */ - u32 magic = 0; - PartitionFileSystemHeader pfs0_header = {0}; - PartitionFileSystemEntry *main_npdm_entry = NULL; - - if (!ncaReadFsSection(nca_fs_ctx, &pfs0_header, sizeof(PartitionFileSystemHeader), out->offset)) - { - LOGFILE("Failed to read partial PFS0 header!"); - return false; - } - - magic = __builtin_bswap32(pfs0_header.magic); - if (magic != PFS0_MAGIC) - { - LOGFILE("Invalid PFS0 magic word! (0x%08X)", magic); - return false; - } - - if (!pfs0_header.entry_count || !pfs0_header.name_table_size) - { - LOGFILE("Invalid PFS0 entry count / name table size!"); - return false; - } - - /* Calculate full PFS0 header size */ - out->header_size = (sizeof(PartitionFileSystemHeader) + (pfs0_header.entry_count * sizeof(PartitionFileSystemEntry)) + pfs0_header.name_table_size); - - /* Allocate memory for the full PFS0 header */ - out->header = calloc(out->header_size, sizeof(u8)); - if (!out->header) - { - LOGFILE("Unable to allocate 0x%lX bytes buffer for the full PFS0 header!", out->header_size); - return false; - } - - /* Read full PFS0 header */ - if (!ncaReadFsSection(nca_fs_ctx, out->header, out->header_size, out->offset)) - { - LOGFILE("Failed to read full PFS0 header!"); - return false; - } - - /* Check if we're dealing with an ExeFS section */ - if ((main_npdm_entry = pfs0GetEntryByName(out, "main.npdm")) != NULL && pfs0ReadEntryData(out, main_npdm_entry, &magic, sizeof(u32), 0) && \ - __builtin_bswap32(magic) == NPDM_META_MAGIC) out->is_exefs = true; - - return true; -} - -bool pfs0ReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, void *out, u64 read_size, u64 offset) -{ - if (!ctx || !ctx->nca_fs_ctx || !ctx->hash_info || !ctx->size || !ctx->header_size || !ctx->header || !fs_entry || fs_entry->offset >= ctx->size || \ - (fs_entry->offset + fs_entry->size) > ctx->size || !out || !read_size || offset >= fs_entry->size || (offset + read_size) > fs_entry->size) - { - LOGFILE("Invalid parameters!"); - return false; - } - - /* Calculate offset relative to the start of the NCA FS section */ - u64 section_offset = (ctx->offset + ctx->header_size + fs_entry->offset + offset); - - /* Read entry data */ - if (!ncaReadFsSection(ctx->nca_fs_ctx, out, read_size, section_offset)) - { - LOGFILE("Failed to read PFS0 entry data!"); - return false; - } - - return true; -} diff --git a/source/pfs0.h b/source/pfs0.h deleted file mode 100644 index e0cd171..0000000 --- a/source/pfs0.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2020 DarkMatterCore - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#ifndef __PFS0_H__ -#define __PFS0_H__ - -#include -#include "nca.h" - -#define PFS0_MAGIC 0x50465330 /* "PFS0" */ - -typedef struct { - u32 magic; ///< "PFS0". - u32 entry_count; - u32 name_table_size; - u8 reserved[0x4]; -} PartitionFileSystemHeader; - -typedef struct { - u64 offset; - u64 size; - u32 name_offset; - u8 reserved[0x4]; -} PartitionFileSystemEntry; - -typedef struct { - NcaFsSectionContext *nca_fs_ctx; ///< Used to read NCA FS section data. - NcaHierarchicalSha256 *hash_info; ///< Hash table information. - u64 offset; ///< Partition offset (relative to the start of the NCA FS section). - u64 size; ///< Partition size. - bool is_exefs; ///< ExeFS flag. - u64 header_size; ///< Full header size. - u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table. -} PartitionFileSystemContext; - -/// Initializes a PFS0 context. -bool pfs0InitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx); - -/// Cleanups a previously initialized PFS0 context. -static inline void pfs0FreeContext(PartitionFileSystemContext *ctx) -{ - if (!ctx) return; - if (ctx->header) free(ctx->header); - memset(ctx, 0, sizeof(PartitionFileSystemContext)); -} - -/// Reads data from a previously retrieved PartitionFileSystemEntry using a PFS0 context. -bool pfs0ReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, void *out, u64 read_size, u64 offset); - -/// Miscellaneous functions. - -static inline u32 pfs0GetEntryCount(PartitionFileSystemContext *ctx) -{ - if (!ctx || !ctx->header_size || !ctx->header) return 0; - return ((PartitionFileSystemHeader*)ctx->header)->entry_count; -} - -static inline PartitionFileSystemEntry *pfs0GetEntryByIndex(PartitionFileSystemContext *ctx, u32 idx) -{ - if (idx >= pfs0GetEntryCount(ctx)) return NULL; - return (PartitionFileSystemEntry*)(ctx->header + sizeof(PartitionFileSystemHeader) + (idx * sizeof(PartitionFileSystemEntry))); -} - -static inline char *pfs0GetNameTable(PartitionFileSystemContext *ctx) -{ - u32 entry_count = pfs0GetEntryCount(ctx); - if (!entry_count) return NULL; - return (char*)(ctx->header + sizeof(PartitionFileSystemHeader) + (entry_count * sizeof(PartitionFileSystemEntry))); -} - -static inline char *pfs0GetEntryNameByIndex(PartitionFileSystemContext *ctx, u32 idx) -{ - PartitionFileSystemEntry *fs_entry = pfs0GetEntryByIndex(ctx, idx); - char *name_table = pfs0GetNameTable(ctx); - if (!fs_entry || !name_table) return NULL; - return (name_table + fs_entry->name_offset); -} - -static inline bool pfs0GetEntryIndexByName(PartitionFileSystemContext *ctx, const char *name, u32 *out_idx) -{ - size_t name_len = 0; - u32 entry_count = pfs0GetEntryCount(ctx); - char *name_table = pfs0GetNameTable(ctx); - if (!entry_count || !name_table || !name || !(name_len = strlen(name)) || !out_idx) return false; - - for(u32 i = 0; i < entry_count; i++) - { - PartitionFileSystemEntry *fs_entry = pfs0GetEntryByIndex(ctx, i); - if (!fs_entry) continue; - - if (!strncmp(name_table + fs_entry->name_offset, name, name_len)) - { - *out_idx = i; - return true; - } - } - - return false; -} - -static inline PartitionFileSystemEntry *pfs0GetEntryByName(PartitionFileSystemContext *ctx, const char *name) -{ - u32 idx = 0; - if (!pfs0GetEntryIndexByName(ctx, name, &idx)) return NULL; - return pfs0GetEntryByIndex(ctx, idx); -} - -#endif /* __PFS0_H__ */ diff --git a/source/romfs.c b/source/romfs.c new file mode 100644 index 0000000..43dab43 --- /dev/null +++ b/source/romfs.c @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2020 DarkMatterCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "romfs.h" +#include "utils.h" + +bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx) +{ + NcaContext *nca_ctx = NULL; + u64 dir_table_offset = 0, file_table_offset = 0; + + if (!out || !nca_fs_ctx || nca_fs_ctx->section_type != NcaFsSectionType_RomFs || !nca_fs_ctx->header || !(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))) + { + LOGFILE("Invalid parameters!"); + return false; + } + + /* Fill context */ + out->nca_fs_ctx = nca_fs_ctx; + out->offset = 0; + out->size = 0; + out->dir_table_size = 0; + out->dir_table = NULL; + out->file_table_size = 0; + out->file_table = NULL; + out->body_offset = 0; + + if (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs) + { + out->sha256_hash_info = &(nca_fs_ctx->header->hash_info.hierarchical_sha256); + out->integrity_hash_info = NULL; + + if (!ncaValidateHierarchicalSha256Offsets(out->sha256_hash_info, nca_fs_ctx->section_size)) + { + LOGFILE("Invalid HierarchicalSha256 block!"); + return false; + } + + out->offset = out->sha256_hash_info->hash_target_layer_info.offset; + out->size = out->sha256_hash_info->hash_target_layer_info.size; + } else { + out->sha256_hash_info = NULL; + out->integrity_hash_info = &(nca_fs_ctx->header->hash_info.hierarchical_integrity); + + if (!ncaValidateHierarchicalIntegrityOffsets(out->integrity_hash_info, nca_fs_ctx->section_size)) + { + LOGFILE("Invalid HierarchicalIntegrity block!"); + return false; + } + + out->offset = out->integrity_hash_info->hash_target_layer_info.offset; + out->size = out->integrity_hash_info->hash_target_layer_info.size; + } + + /* Read RomFS header */ + if (!ncaReadFsSection(nca_fs_ctx, &(out->header), sizeof(RomFileSystemHeader), out->offset)) + { + LOGFILE("Failed to read RomFS header!"); + return false; + } + + if ((nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs && out->header.old_format.header_size != ROMFS_OLD_HEADER_SIZE) || \ + (nca_fs_ctx->section_type == NcaFsSectionType_RomFs && out->header.cur_format.header_size != ROMFS_HEADER_SIZE)) + { + LOGFILE("Invalid RomFS header size!"); + return false; + } + + /* Read directory entries table */ + dir_table_offset = (out->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 (dir_table_offset >= out->size || !out->dir_table_size || (dir_table_offset + out->dir_table_size) > out->size) + { + LOGFILE("Invalid RomFS directory entries table!"); + return false; + } + + out->dir_table = malloc(out->dir_table_size); + if (!out->dir_table) + { + LOGFILE("Unable to allocate memory for RomFS directory entries table!"); + return false; + } + + if (!ncaReadFsSection(nca_fs_ctx, out->dir_table, out->dir_table_size, dir_table_offset)) + { + LOGFILE("Failed to read RomFS directory entries table!"); + return false; + } + + /* Read file entries table */ + file_table_offset = (out->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 (file_table_offset >= out->size || !out->file_table_size || (file_table_offset + out->file_table_size) > out->size) + { + LOGFILE("Invalid RomFS file entries table!"); + return false; + } + + out->file_table = malloc(out->file_table_size); + if (!out->file_table) + { + LOGFILE("Unable to allocate memory for RomFS file entries table!"); + return false; + } + + if (!ncaReadFsSection(nca_fs_ctx, out->file_table, out->file_table_size, file_table_offset)) + { + LOGFILE("Failed to read RomFS file entries table!"); + return false; + } + + /* Calculate file data body offset */ + out->body_offset = (out->offset + (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.body_offset : out->header.cur_format.body_offset)); + if (out->body_offset >= out->size) + { + LOGFILE("Invalid RomFS file data body!"); + return false; + } + + return true; +} + +bool romfsReadFileEntryData(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, void *out, u64 read_size, u64 offset) +{ + if (!ctx || !ctx->nca_fs_ctx || !ctx->size || !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) + { + LOGFILE("Invalid parameters!"); + return false; + } + + /* Calculate offset relative to the start of the NCA FS section */ + u64 section_offset = (ctx->body_offset + file_entry->offset + offset); + + /* Read entry data */ + if (!ncaReadFsSection(ctx->nca_fs_ctx, out, read_size, section_offset)) + { + LOGFILE("Failed to read RomFS file entry data!"); + return false; + } + + return true; +} + +bool romfsGetTotalDataSize(RomFileSystemContext *ctx, u64 *out_size) +{ + if (!ctx || !ctx->file_table_size || !ctx->file_table || !out_size) return false; + + u64 offset = 0, total_size = 0; + RomFileSystemFileEntry *file_entry = NULL; + + while(offset < ctx->file_table_size) + { + if (!(file_entry = romfsGetFileEntry(ctx, offset))) return false; + total_size += file_entry->size; + offset += ALIGN_UP(sizeof(RomFileSystemFileEntry) + file_entry->name_length, 4); + } + + *out_size = total_size; + return true; +} + +bool romfsGetDirectoryDataSize(RomFileSystemContext *ctx, u32 dir_entry_offset, u64 *out_size) +{ + u64 total_size = 0, child_dir_size = 0; + RomFileSystemDirectoryEntry *dir_entry = NULL; + RomFileSystemFileEntry *file_entry = NULL; + + if (!ctx || !ctx->dir_table_size || !ctx->dir_table || !ctx->file_table_size || !ctx->file_table || !out_size || !(dir_entry = romfsGetDirectoryEntry(ctx, dir_entry_offset)) || \ + (!dir_entry->name_length && dir_entry_offset > 0)) return false; + + if (dir_entry->file_offset != ROMFS_VOID_ENTRY) + { + if (!(file_entry = romfsGetFileEntry(ctx, dir_entry->file_offset))) return false; + total_size += file_entry->size; + + while(file_entry->next_offset != ROMFS_VOID_ENTRY) + { + if (!(file_entry = romfsGetFileEntry(ctx, file_entry->next_offset))) return false; + total_size += file_entry->size; + } + } + + if (dir_entry->directory_offset != ROMFS_VOID_ENTRY) + { + if (!romfsGetDirectoryDataSize(ctx, dir_entry->directory_offset, &child_dir_size)) return false; + total_size += child_dir_size; + + while(dir_entry->next_offset != ROMFS_VOID_ENTRY) + { + if (!romfsGetDirectoryDataSize(ctx, dir_entry->next_offset, &child_dir_size)) return false; + total_size += child_dir_size; + if (!(dir_entry = romfsGetDirectoryEntry(ctx, dir_entry->next_offset))) return false; + } + } + + *out_size = total_size; + + return true; +} diff --git a/source/romfs.h b/source/romfs.h new file mode 100644 index 0000000..7d0a2fc --- /dev/null +++ b/source/romfs.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2020 DarkMatterCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#ifndef __ROMFS_H__ +#define __ROMFS_H__ + +#include +#include "nca.h" + +#define ROMFS_OLD_HEADER_SIZE 0x28 +#define ROMFS_HEADER_SIZE 0x50 + +#define ROMFS_VOID_ENTRY 0xFFFFFFFF + +/// Header used by NCA0 RomFS sections. +typedef struct { + u32 header_size; ///< Header size. Must be equal to ROMFS_OLD_HEADER_SIZE. + u32 directory_bucket_offset; ///< Directory buckets table offset. + u32 directory_bucket_size; ///< Directory buckets table size. + u32 directory_entry_offset; ///< Directory entries table offset. + u32 directory_entry_size; ///< Directory entries table size. + u32 file_bucket_offset; ///< File buckets table offset. + u32 file_bucket_size; ///< File buckets table size. + u32 file_entry_offset; ///< File entries table offset. + u32 file_entry_size; ///< File entries table size. + u32 body_offset; ///< File data body offset. +} RomFileSystemInformationOld; + +/// Header used by NCA2/NCA3 RomFS sections. +typedef struct { + u64 header_size; ///< Header size. Must be equal to ROMFS_HEADER_SIZE. + u64 directory_bucket_offset; ///< Directory buckets table offset. + u64 directory_bucket_size; ///< Directory buckets table size. + u64 directory_entry_offset; ///< Directory entries table offset. + u64 directory_entry_size; ///< Directory entries table size. + u64 file_bucket_offset; ///< File buckets table offset. + u64 file_bucket_size; ///< File buckets table size. + u64 file_entry_offset; ///< File entries table offset. + u64 file_entry_size; ///< File entries table size. + u64 body_offset; ///< File data body offset. +} RomFileSystemInformation; + +/// Header union. +typedef struct { + union { + struct { + RomFileSystemInformationOld old_format; + u8 padding[ROMFS_OLD_HEADER_SIZE]; + }; + RomFileSystemInformation cur_format; + }; +} RomFileSystemHeader; + +/// Directory entry. Always aligned to a 4-byte boundary past the directory name. +typedef struct { + u32 parent_offset; ///< Parent directory offset. + u32 next_offset; ///< Next sibling directory offset. + u32 directory_offset; ///< First child directory offset. + u32 file_offset; ///< First child file offset. + u32 bucket_offset; ///< Directory bucket offset. + u32 name_length; ///< Name length. + char name[]; ///< Name (UTF-8). +} RomFileSystemDirectoryEntry; + +/// Directory entry. Always aligned to a 4-byte boundary past the file name. +typedef struct { + u32 parent_offset; ///< Parent directory offset. + u32 next_offset; ///< Next sibling file offset. + u64 offset; ///< File data offset. + u64 size; ///< File data size. + u32 bucket_offset; ///< File bucket offset. + u32 name_length; ///< Name length. + char name[]; ///< Name (UTF-8). +} RomFileSystemFileEntry; + +typedef struct { + NcaFsSectionContext *nca_fs_ctx; ///< Used to read NCA FS section data. + NcaHierarchicalSha256 *sha256_hash_info; ///< HierarchicalSha256 hash table information. Used with NCA0 RomFS sections. + NcaHierarchicalIntegrity *integrity_hash_info; ///< HierarchicalIntegrity hash table information. Used with NCA2/NCA3 RomFS sections. + u64 offset; ///< RomFS offset (relative to the start of the NCA FS section). + u64 size; ///< RomFS size. + RomFileSystemHeader header; ///< RomFS header. + u64 dir_table_size; ///< RomFS directory entries table size. + RomFileSystemDirectoryEntry *dir_table; ///< RomFS directory entries table. + u64 file_table_size; ///< RomFS file entries table size. + RomFileSystemFileEntry *file_table; ///< RomFS file entries table. + u64 body_offset; ///< RomFS file data body offset (relative to the start of the NCA FS section). +} RomFileSystemContext; + +/// Initializes a RomFS context. +bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx); + +/// Cleanups a previously initialized RomFS context. +NX_INLINE void romfsFreeContext(RomFileSystemContext *ctx) +{ + if (!ctx) return; + if (ctx->dir_table) free(ctx->dir_table); + if (ctx->file_table) free(ctx->file_table); + memset(ctx, 0, sizeof(RomFileSystemContext)); +} + +/// Reads data from a previously retrieved RomFileSystemFileEntry using a RomFS context. +bool romfsReadFileEntryData(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, void *out, u64 read_size, u64 offset); + +/// Calculates the extracted RomFS size. +bool romfsGetTotalDataSize(RomFileSystemContext *ctx, u64 *out_size); + +/// Calculates the extracted size from a RomFS directory. +bool romfsGetDirectoryDataSize(RomFileSystemContext *ctx, u32 dir_entry_offset, u64 *out_size); + +/// Miscellaneous functions. + +NX_INLINE RomFileSystemDirectoryEntry *romfsGetDirectoryEntry(RomFileSystemContext *ctx, u32 file_entry_offset) +{ + if (!ctx || !ctx->dir_table || file_entry_offset >= ctx->dir_table_size) return NULL; + return (RomFileSystemDirectoryEntry*)((u8*)ctx->dir_table + file_entry_offset); +} + +NX_INLINE RomFileSystemFileEntry *romfsGetFileEntry(RomFileSystemContext *ctx, u32 dir_entry_offset) +{ + if (!ctx || !ctx->file_table || dir_entry_offset >= ctx->file_table_size) return NULL; + return (RomFileSystemFileEntry*)((u8*)ctx->file_table + dir_entry_offset); +} + +#endif /* __ROMFS_H__ */ diff --git a/source/utils.h b/source/utils.h index 235b946..e2f5161 100644 --- a/source/utils.h +++ b/source/utils.h @@ -29,11 +29,13 @@ #define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0]))) -#define ROUND_DOWN(x, y) ((x) & ~((y) - 1)) -#define ROUND_UP(x, y) ((x) + (((y) - ((x) % (y))) % (y))) +#define ALIGN_DOWN(x, y) ((x) & ~((y) - 1)) +#define ALIGN_UP(x, y) ((((y) - 1) + (x)) & ~((y) - 1)) #define BIS_SYSTEM_PARTITION_MOUNT_NAME "sys:" +#define NPDM_META_MAGIC 0x4D455441 /* "META" */ + typedef enum { UtilsCustomFirmwareType_Unknown = 0, UtilsCustomFirmwareType_Atmosphere = 1,