1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-29 13:42:11 +00:00

NPDM context.

This commit is contained in:
Pablo Curiel 2020-10-10 15:29:14 -04:00
parent dcbedbf13a
commit 2066b11d5a
3 changed files with 308 additions and 9 deletions

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);