diff --git a/source/cnmt.c b/source/cnmt.c index d36d5f4..21a389b 100644 --- a/source/cnmt.c +++ b/source/cnmt.c @@ -220,7 +220,7 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx) /* Safety check: verify raw CNMT size. */ if (cur_offset != out->raw_data_size) { - LOGFILE("Raw CNMT size mismatch! (0x%X != 0x%X).", cur_offset, out->raw_data_size); + LOGFILE("Raw CNMT size mismatch! (0x%lX != 0x%lX).", cur_offset, out->raw_data_size); goto end; } diff --git a/source/npdm.c b/source/npdm.c index a798163..f1d4494 100644 --- a/source/npdm.c +++ b/source/npdm.c @@ -21,3 +21,241 @@ #include "utils.h" #include "npdm.h" +bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx) +{ + NcaContext *nca_ctx = NULL; + u64 cur_offset = 0; + bool success = false; + + if (!out || !pfs_ctx || !pfs_ctx->nca_fs_ctx || !(nca_ctx = (NcaContext*)pfs_ctx->nca_fs_ctx->nca_ctx) || nca_ctx->content_type != NcmContentType_Program || !pfs_ctx->offset || !pfs_ctx->size || \ + !pfs_ctx->is_exefs || pfs_ctx->header_size <= sizeof(PartitionFileSystemHeader) || !pfs_ctx->header) + { + LOGFILE("Invalid parameters!"); + return false; + } + + /* Free output context beforehand. */ + npdmFreeContext(out); + + /* Get 'main.npdm' file entry. */ + out->pfs_ctx = pfs_ctx; + if (!(out->pfs_entry = pfsGetEntryByName(out->pfs_ctx, "main.npdm"))) + { + LOGFILE("'main.npdm' entry unavailable in ExeFS!"); + goto end; + } + + //LOGFILE("Found 'main.npdm' entry in Program NCA \"%s\".", nca_ctx->content_id_str); + + /* Check raw NPDM size. */ + if (!out->pfs_entry->size) + { + LOGFILE("Invalid raw NPDM size!"); + goto end; + } + + /* Allocate memory for the raw NPDM data. */ + out->raw_data_size = out->pfs_entry->size; + if (!(out->raw_data = malloc(out->raw_data_size))) + { + LOGFILE("Failed to allocate memory for the raw NPDM data!"); + goto end; + } + + /* Read raw NPDM data into memory buffer. */ + if (!pfsReadEntryData(out->pfs_ctx, out->pfs_entry, out->raw_data, out->raw_data_size, 0)) + { + LOGFILE("Failed to read raw NPDM data!"); + 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); + + if (__builtin_bswap32(out->meta_header->magic) != NPDM_META_MAGIC) + { + LOGFILE("Invalid meta header magic word! (0x%08X != 0x%08X).", __builtin_bswap32(out->meta_header->magic), __builtin_bswap32(NPDM_META_MAGIC)); + goto end; + } + + if (!out->meta_header->flags.is_64bit_instruction && (out->meta_header->flags.process_address_space == NpdmProcessAddressSpace_AddressSpace64BitOld || \ + out->meta_header->flags.process_address_space == NpdmProcessAddressSpace_AddressSpace64Bit)) + { + LOGFILE("Invalid meta header flags! (0x%02X).", *((u8*)&(out->meta_header->flags))); + goto end; + } + + if (out->meta_header->main_thread_priority > NPDM_MAIN_THREAD_MAX_PRIORITY) + { + LOGFILE("Invalid main thread priority! (0x%02X).", out->meta_header->main_thread_priority); + goto end; + } + + if (out->meta_header->main_thread_core_number > NPDM_MAIN_THREAD_MAX_CORE_NUMBER) + { + LOGFILE("Invalid main thread core number! (%u).", out->meta_header->main_thread_core_number); + goto end; + } + + if (out->meta_header->system_resource_size > NPDM_SYSTEM_RESOURCE_MAX_SIZE) + { + LOGFILE("Invalid system resource size! (0x%08X).", out->meta_header->system_resource_size); + goto end; + } + + if (!IS_ALIGNED(out->meta_header->main_thread_stack_size, NPDM_MAIN_THREAD_STACK_SIZE_ALIGNMENT)) + { + LOGFILE("Invalid main thread stack size! (0x%08X).", out->meta_header->main_thread_stack_size); + goto end; + } + + if (out->meta_header->aci_offset < sizeof(NpdmMetaHeader) || out->meta_header->aci_size < sizeof(NpdmAciHeader) || (out->meta_header->aci_offset + out->meta_header->aci_size) > out->raw_data_size) + { + LOGFILE("Invalid ACI0 offset/size! (0x%08X, 0x%08X).", out->meta_header->aci_offset, out->meta_header->aci_size); + goto end; + } + + if (out->meta_header->acid_offset < sizeof(NpdmMetaHeader) || out->meta_header->acid_size < sizeof(NpdmAcidHeader) || (out->meta_header->acid_offset + out->meta_header->acid_size) > out->raw_data_size) + { + LOGFILE("Invalid ACID offset/size! (0x%08X, 0x%08X).", out->meta_header->acid_offset, out->meta_header->acid_size); + goto end; + } + + if (out->meta_header->aci_offset == out->meta_header->acid_offset || \ + (out->meta_header->aci_offset > out->meta_header->acid_offset && out->meta_header->aci_offset < (out->meta_header->acid_offset + out->meta_header->acid_size)) || \ + (out->meta_header->acid_offset > out->meta_header->aci_offset && out->meta_header->acid_offset < (out->meta_header->aci_offset + out->meta_header->aci_size))) + { + LOGFILE("ACI0/ACID sections overlap! (0x%08X, 0x%08X | 0x%08X, 0x%08X).", out->meta_header->aci_offset, out->meta_header->aci_size, out->meta_header->acid_offset, out->meta_header->acid_size); + goto end; + } + + /* Verify ACID section. */ + out->acid_header = (NpdmAcidHeader*)(out->raw_data + out->meta_header->acid_offset); + cur_offset += out->meta_header->acid_size; + + if (__builtin_bswap32(out->acid_header->magic) != NPDM_ACID_MAGIC) + { + LOGFILE("Invalid ACID header magic word! (0x%08X != 0x%08X).", __builtin_bswap32(out->acid_header->magic), __builtin_bswap32(NPDM_ACID_MAGIC)); + goto end; + } + + if (out->acid_header->size != (out->meta_header->acid_size - sizeof(out->acid_header->signature))) + { + LOGFILE("Invalid ACID header size! (0x%08X).", out->acid_header->size); + goto end; + } + + if (out->acid_header->program_id_min > out->acid_header->program_id_max) + { + LOGFILE("Invalid ACID program ID range! (%016lX - %016lX).", out->acid_header->program_id_min, out->acid_header->program_id_max); + goto end; + } + + if (out->acid_header->fs_access_control_offset < sizeof(NpdmAcidHeader) || out->acid_header->fs_access_control_size < sizeof(NpdmAcidFsAccessControlDescriptor) || \ + (out->acid_header->fs_access_control_offset + out->acid_header->fs_access_control_size) > out->meta_header->acid_size) + { + LOGFILE("Invalid ACID FsAccessControl offset/size! (0x%08X, 0x%08X).", out->acid_header->fs_access_control_offset, out->acid_header->fs_access_control_size); + goto end; + } + + out->acid_fac_descriptor = (NpdmAcidFsAccessControlDescriptor*)(out->raw_data + out->meta_header->acid_offset + out->acid_header->fs_access_control_offset); + + if (out->acid_header->srv_access_control_size) + { + if (out->acid_header->srv_access_control_offset < sizeof(NpdmAcidHeader) || \ + (out->acid_header->srv_access_control_offset + out->acid_header->srv_access_control_size) > out->meta_header->acid_size) + { + LOGFILE("Invalid ACID SrvAccessControl offset/size! (0x%08X, 0x%08X).", out->acid_header->srv_access_control_offset, out->acid_header->srv_access_control_size); + goto end; + } + + out->acid_sac_descriptor = (NpdmSrvAccessControlDescriptorEntry*)(out->raw_data + out->meta_header->acid_offset + out->acid_header->srv_access_control_offset); + } + + if (out->acid_header->kernel_capability_size) + { + if (!IS_ALIGNED(out->acid_header->kernel_capability_size, sizeof(NpdmKernelCapabilityDescriptorEntry)) || \ + out->acid_header->kernel_capability_offset < sizeof(NpdmAcidHeader) || \ + (out->acid_header->kernel_capability_offset + out->acid_header->kernel_capability_size) > out->meta_header->acid_size) + { + LOGFILE("Invalid ACID KernelCapability offset/size! (0x%08X, 0x%08X).", out->acid_header->kernel_capability_offset, out->acid_header->kernel_capability_size); + goto end; + } + + out->acid_kc_descriptor = (NpdmKernelCapabilityDescriptorEntry*)(out->raw_data + out->meta_header->acid_offset + out->acid_header->kernel_capability_offset); + } + + /* Verify ACI0 section. */ + out->aci_header = (NpdmAciHeader*)(out->raw_data + out->meta_header->aci_offset); + cur_offset += out->meta_header->aci_size; + + if (__builtin_bswap32(out->aci_header->magic) != NPDM_ACI0_MAGIC) + { + LOGFILE("Invalid ACI0 header magic word! (0x%08X != 0x%08X).", __builtin_bswap32(out->aci_header->magic), __builtin_bswap32(NPDM_ACI0_MAGIC)); + goto end; + } + + if (out->aci_header->program_id != nca_ctx->header.program_id) + { + LOGFILE("ACI0 program ID mismatch! (%016lX != %016lX).", out->aci_header->program_id, nca_ctx->header.program_id); + goto end; + } + + if (out->aci_header->program_id < out->acid_header->program_id_min || out->aci_header->program_id > out->acid_header->program_id_max) + { + LOGFILE("ACI0 program ID out of ACID program ID range! (%016lX, %016lX - %016lX).", out->aci_header->program_id, out->acid_header->program_id_min, out->acid_header->program_id_max); + goto end; + } + + if (out->aci_header->fs_access_control_offset < sizeof(NpdmAciHeader) || out->aci_header->fs_access_control_size < sizeof(NpdmAciFsAccessControlDescriptor) || \ + (out->aci_header->fs_access_control_offset + out->aci_header->fs_access_control_size) > out->meta_header->aci_size) + { + LOGFILE("Invalid ACI0 FsAccessControl offset/size! (0x%08X, 0x%08X).", out->aci_header->fs_access_control_offset, out->aci_header->fs_access_control_size); + goto end; + } + + out->aci_fac_descriptor = (NpdmAciFsAccessControlDescriptor*)(out->raw_data + out->meta_header->aci_offset + out->aci_header->fs_access_control_offset); + + if (out->aci_header->srv_access_control_size) + { + if (out->aci_header->srv_access_control_offset < sizeof(NpdmAciHeader) || \ + (out->aci_header->srv_access_control_offset + out->aci_header->srv_access_control_size) > out->meta_header->aci_size) + { + LOGFILE("Invalid ACI0 SrvAccessControl offset/size! (0x%08X, 0x%08X).", out->aci_header->srv_access_control_offset, out->aci_header->srv_access_control_size); + goto end; + } + + out->aci_sac_descriptor = (NpdmSrvAccessControlDescriptorEntry*)(out->raw_data + out->meta_header->aci_offset + out->aci_header->srv_access_control_offset); + } + + if (out->aci_header->kernel_capability_size) + { + if (!IS_ALIGNED(out->aci_header->kernel_capability_size, sizeof(NpdmKernelCapabilityDescriptorEntry)) || \ + out->aci_header->kernel_capability_offset < sizeof(NpdmAciHeader) || \ + (out->aci_header->kernel_capability_offset + out->aci_header->kernel_capability_size) > out->meta_header->aci_size) + { + LOGFILE("Invalid ACI0 KernelCapability offset/size! (0x%08X, 0x%08X).", out->aci_header->kernel_capability_offset, out->aci_header->kernel_capability_size); + goto end; + } + + out->aci_kc_descriptor = (NpdmKernelCapabilityDescriptorEntry*)(out->raw_data + out->meta_header->aci_offset + out->aci_header->kernel_capability_offset); + } + + /* Safety check: verify raw NPDM size. */ + if (out->raw_data_size < cur_offset) + { + LOGFILE("Invalid raw NPDM size! (0x%lX < 0x%lX).", out->raw_data_size, cur_offset); + goto end; + } + + success = true; + +end: + if (!success) npdmFreeContext(out); + + return success; +} diff --git a/source/npdm.h b/source/npdm.h index 2b5e406..8549d88 100644 --- a/source/npdm.h +++ b/source/npdm.h @@ -23,9 +23,16 @@ #ifndef __NPDM_H__ #define __NPDM_H__ -#define NPDM_META_MAGIC 0x4D455441 /* "META". */ -#define NPDM_ACID_MAGIC 0x41434944 /* "ACID". */ -#define NPDM_ACI0_MAGIC 0x41434930 /* "ACI0". */ +#include "pfs.h" + +#define NPDM_META_MAGIC 0x4D455441 /* "META". */ +#define NPDM_ACID_MAGIC 0x41434944 /* "ACID". */ +#define NPDM_ACI0_MAGIC 0x41434930 /* "ACI0". */ + +#define NPDM_MAIN_THREAD_MAX_PRIORITY 0x3F +#define NPDM_MAIN_THREAD_MAX_CORE_NUMBER 3 +#define NPDM_SYSTEM_RESOURCE_MAX_SIZE 0x1FE00000 +#define NPDM_MAIN_THREAD_STACK_SIZE_ALIGNMENT 0x1000 typedef enum { NpdmProcessAddressSpace_AddressSpace32Bit = 0, @@ -49,12 +56,12 @@ typedef struct { u8 reserved_1[0x7]; NpdmMetaFlags flags; u8 reserved_2; - u8 main_thread_priority; ///< Ranges from 0x00 to 0x3F. - u8 main_thread_core_number; ///< CPU ID. Ranges from 0 to 3. + u8 main_thread_priority; ///< Must not exceed NPDM_MAIN_THREAD_MAX_PRIORITY. + u8 main_thread_core_number; ///< Must not exceed NPDM_MAIN_THREAD_MAX_CORE_NUMBER. u8 reserved_3[0x4]; - u32 system_resource_size; ///< Must not exceed 0x1FE00000. + u32 system_resource_size; ///< Must not exceed NPDM_SYSTEM_RESOURCE_MAX_SIZE. VersionType1 version; - u32 main_thread_stack_size; ///< Must be aligned to 0x1000. + u32 main_thread_stack_size; ///< Must be aligned to NPDM_MAIN_THREAD_STACK_SIZE_ALIGNMENT. char name[0x10]; ///< Usually set to "Application". char product_code[0x10]; ///< Usually zeroed out. u8 reserved_4[0x30]; @@ -526,7 +533,61 @@ typedef struct { u32 value; } NpdmKernelCapabilityDescriptorEntry; -/// Returns a value that can be compared to values from the NpdmKernelCapabilityEntryValue enum. +typedef struct { + PartitionFileSystemContext *pfs_ctx; ///< PartitionFileSystemContext for the Program NCA FS section #0, which is where the NPDM is stored. + PartitionFileSystemEntry *pfs_entry; ///< PartitionFileSystemEntry for the NPDM in the Program NCA FS section #0. Used to generate a NcaHierarchicalSha256Patch if needed. + NcaHierarchicalSha256Patch nca_patch; ///< NcaHierarchicalSha256Patch generated if NPDM modifications are needed. Used to seamlessly replace Program NCA data while writing it. + ///< 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'. + NpdmAcidFsAccessControlDescriptor *acid_fac_descriptor; ///< Pointer to the NpdmAcidFsAccessControlDescriptor within the NPDM ACID section. + NpdmSrvAccessControlDescriptorEntry *acid_sac_descriptor; ///< Pointer to the first NpdmSrvAccessControlDescriptorEntry within the NPDM ACID section, if available. + NpdmKernelCapabilityDescriptorEntry *acid_kc_descriptor; ///< Pointer to the first NpdmKernelCapabilityDescriptorEntry within the NPDM ACID section, if available. + NpdmAciHeader *aci_header; ///< Pointer to the NpdmAciHeader within 'raw_data'. + NpdmAciFsAccessControlDescriptor *aci_fac_descriptor; ///< Pointer to the NpdmAciFsAccessControlDescriptor within the NPDM ACI0 section. + NpdmSrvAccessControlDescriptorEntry *aci_sac_descriptor; ///< Pointer to the first NpdmSrvAccessControlDescriptorEntry within the NPDM ACI0 section, if available. + NpdmKernelCapabilityDescriptorEntry *aci_kc_descriptor; ///< Pointer to the first NpdmKernelCapabilityDescriptorEntry within the NPDM ACI0 section, if available. +} NpdmContext; + +/// 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); + +/// Helper inline functions. + +NX_INLINE void npdmFreeContext(NpdmContext *npdm_ctx) +{ + if (!npdm_ctx) return; + pfsFreeEntryPatch(&(npdm_ctx->nca_patch)); + if (npdm_ctx->raw_data) free(npdm_ctx->raw_data); + memset(npdm_ctx, 0, sizeof(NpdmContext)); +} + +NX_INLINE bool npdmIsValidContext(NpdmContext *npdm_ctx) +{ + return (npdm_ctx && npdm_ctx->pfs_ctx && npdm_ctx->pfs_entry && npdm_ctx->raw_data && npdm_ctx->raw_data_size && npdm_ctx->meta_header && npdm_ctx->acid_header && npdm_ctx->acid_fac_descriptor && \ + ((npdm_ctx->acid_header->srv_access_control_size && npdm_ctx->acid_sac_descriptor) || (!npdm_ctx->acid_header->srv_access_control_size && !npdm_ctx->acid_sac_descriptor)) && \ + ((npdm_ctx->acid_header->kernel_capability_size && npdm_ctx->acid_kc_descriptor) || (!npdm_ctx->acid_header->kernel_capability_size && !npdm_ctx->acid_kc_descriptor)) && \ + npdm_ctx->aci_header && npdm_ctx->aci_fac_descriptor && \ + ((npdm_ctx->aci_header->srv_access_control_size && npdm_ctx->aci_sac_descriptor) || (!npdm_ctx->aci_header->srv_access_control_size && !npdm_ctx->aci_sac_descriptor)) && \ + ((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);