mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-23 02:36:41 +00:00
Code cleanup.
* Added 'encrypted_header' members to both NcaContext and NcaFsSectionContext. In-place re-encryption isn't desirable in this case. * Fixed FsAccessControl-related type naming for ACI0 blocks.
This commit is contained in:
parent
42fef7d3f1
commit
ba4fdcd01c
12 changed files with 149 additions and 134 deletions
|
@ -313,7 +313,7 @@ int main(int argc, char *argv[])
|
||||||
} else
|
} else
|
||||||
if (menu == 2)
|
if (menu == 2)
|
||||||
{
|
{
|
||||||
printf("fs section #%u (%s)\n", i + 1, ncaGetFsSectionTypeName(&(nca_ctx->fs_contexts[i])));
|
printf("fs section #%u (%s)\n", i + 1, ncaGetFsSectionTypeName(&(nca_ctx->fs_ctx[i])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +367,7 @@ int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
consoleClear();
|
consoleClear();
|
||||||
utilsChangeHomeButtonBlockStatus(true);
|
utilsChangeHomeButtonBlockStatus(true);
|
||||||
dumpFsSection(cur_title_info, &(nca_ctx->fs_contexts[selected_idx]));
|
dumpFsSection(cur_title_info, &(nca_ctx->fs_ctx[selected_idx]));
|
||||||
utilsChangeHomeButtonBlockStatus(false);
|
utilsChangeHomeButtonBlockStatus(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -501,7 +501,7 @@ int main(int argc, char *argv[])
|
||||||
goto out2;
|
goto out2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bktrInitializeContext(&bktr_ctx, &(base_nca_ctx->fs_contexts[1]), &(update_nca_ctx->fs_contexts[1])))
|
if (!bktrInitializeContext(&bktr_ctx, &(base_nca_ctx->fs_ctx[1]), &(update_nca_ctx->fs_ctx[1])))
|
||||||
{
|
{
|
||||||
consolePrint("bktr initialize ctx failed\n");
|
consolePrint("bktr initialize ctx failed\n");
|
||||||
goto out2;
|
goto out2;
|
||||||
|
@ -512,7 +512,7 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
consolePrint("bktr initialize ctx succeeded\n");
|
consolePrint("bktr initialize ctx succeeded\n");
|
||||||
} else {
|
} else {
|
||||||
if (!romfsInitializeContext(&romfs_ctx, &(base_nca_ctx->fs_contexts[1])))
|
if (!romfsInitializeContext(&romfs_ctx, &(base_nca_ctx->fs_ctx[1])))
|
||||||
{
|
{
|
||||||
consolePrint("romfs initialize ctx failed\n");
|
consolePrint("romfs initialize ctx failed\n");
|
||||||
goto out2;
|
goto out2;
|
||||||
|
|
|
@ -53,7 +53,7 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
|
||||||
cnmtFreeContext(out);
|
cnmtFreeContext(out);
|
||||||
|
|
||||||
/* Initialize Partition FS context. */
|
/* Initialize Partition FS context. */
|
||||||
if (!pfsInitializeContext(&(out->pfs_ctx), &(nca_ctx->fs_contexts[0])))
|
if (!pfsInitializeContext(&(out->pfs_ctx), &(nca_ctx->fs_ctx[0])))
|
||||||
{
|
{
|
||||||
LOGFILE("Failed to initialize Partition FS context!");
|
LOGFILE("Failed to initialize Partition FS context!");
|
||||||
goto end;
|
goto end;
|
||||||
|
|
|
@ -107,7 +107,7 @@ typedef enum {
|
||||||
/// Header for the extended data region in the SystemUpdate title, pointed to by the extended header.
|
/// Header for the extended data region in the SystemUpdate title, pointed to by the extended header.
|
||||||
/// If version is ContentMetaFirmwareVariationVersion_V1, this is followed by 'variation_count' ContentMetaFirmwareVariationInfoV1 entries.
|
/// If version is ContentMetaFirmwareVariationVersion_V1, this is followed by 'variation_count' ContentMetaFirmwareVariationInfoV1 entries.
|
||||||
/// Otherwise, if version is ContentMetaFirmwareVariationVersion_V2, this is followed by:
|
/// Otherwise, if version is ContentMetaFirmwareVariationVersion_V2, this is followed by:
|
||||||
/// * 'variation_count' firmware variation IDs.
|
/// * 'variation_count' firmware variation IDs (4 bytes each).
|
||||||
/// * 'variation_count' ContentMetaFirmwareVariationInfoV2 entries.
|
/// * 'variation_count' ContentMetaFirmwareVariationInfoV2 entries.
|
||||||
/// * (Optionally) A variable number of NcmContentMetaInfo entries, which is the sum of all 'meta_count' values from ContentMetaFirmwareVariationInfoV2 entries where 'refer_to_base' is set to false.
|
/// * (Optionally) A variable number of NcmContentMetaInfo entries, which is the sum of all 'meta_count' values from ContentMetaFirmwareVariationInfoV2 entries where 'refer_to_base' is set to false.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
@ -41,7 +41,7 @@ bool legalInfoInitializeContext(LegalInfoContext *out, NcaContext *nca_ctx)
|
||||||
legalInfoFreeContext(out);
|
legalInfoFreeContext(out);
|
||||||
|
|
||||||
/* Initialize RomFS context. */
|
/* Initialize RomFS context. */
|
||||||
if (!romfsInitializeContext(&romfs_ctx, &(nca_ctx->fs_contexts[0])))
|
if (!romfsInitializeContext(&romfs_ctx, &(nca_ctx->fs_ctx[0])))
|
||||||
{
|
{
|
||||||
LOGFILE("Failed to initialize RomFS context!");
|
LOGFILE("Failed to initialize RomFS context!");
|
||||||
goto end;
|
goto end;
|
||||||
|
|
|
@ -216,7 +216,7 @@ bool nacpInitializeContext(NacpContext *out, NcaContext *nca_ctx)
|
||||||
nacpFreeContext(out);
|
nacpFreeContext(out);
|
||||||
|
|
||||||
/* Initialize RomFS context. */
|
/* Initialize RomFS context. */
|
||||||
if (!romfsInitializeContext(&(out->romfs_ctx), &(nca_ctx->fs_contexts[0])))
|
if (!romfsInitializeContext(&(out->romfs_ctx), &(nca_ctx->fs_ctx[0])))
|
||||||
{
|
{
|
||||||
LOGFILE("Failed to initialize RomFS context!");
|
LOGFILE("Failed to initialize RomFS context!");
|
||||||
goto end;
|
goto end;
|
||||||
|
|
126
source/nca.c
126
source/nca.c
|
@ -42,7 +42,7 @@ static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = {
|
||||||
|
|
||||||
NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info);
|
NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info);
|
||||||
|
|
||||||
static bool ncaDecryptHeader(NcaContext *ctx);
|
static bool ncaReadDecryptedHeader(NcaContext *ctx);
|
||||||
static bool ncaDecryptKeyArea(NcaContext *ctx);
|
static bool ncaDecryptKeyArea(NcaContext *ctx);
|
||||||
|
|
||||||
static bool ncaEncryptKeyArea(NcaContext *ctx);
|
static bool ncaEncryptKeyArea(NcaContext *ctx);
|
||||||
|
@ -129,23 +129,13 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read NCA header. */
|
/* Read decrypted NCA header and NCA FS section headers. */
|
||||||
if (!ncaReadContentFile(out, &(out->header), sizeof(NcaHeader), 0))
|
if (!ncaReadDecryptedHeader(out))
|
||||||
{
|
{
|
||||||
LOGFILE("Failed to read NCA \"%s\" header!", out->content_id_str);
|
LOGFILE("Failed to read decrypted NCA \"%s\" header!", out->content_id_str);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Decrypt NCA header. */
|
|
||||||
if (!ncaDecryptHeader(out))
|
|
||||||
{
|
|
||||||
LOGFILE("Failed to decrypt NCA \"%s\" header!", out->content_id_str);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Calculate decrypted header hash. */
|
|
||||||
sha256CalculateHash(out->header_hash, &(out->header), sizeof(NcaHeader));
|
|
||||||
|
|
||||||
if (out->rights_id_available)
|
if (out->rights_id_available)
|
||||||
{
|
{
|
||||||
/* Retrieve ticket. */
|
/* Retrieve ticket. */
|
||||||
|
@ -163,34 +153,37 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
||||||
/* Parse sections. */
|
/* Parse sections. */
|
||||||
for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
||||||
{
|
{
|
||||||
|
NcaFsInfo *fs_info = &(out->header.fs_info[i]);
|
||||||
|
NcaFsSectionContext *fs_ctx = &(out->fs_ctx[i]);
|
||||||
|
|
||||||
/* Fill section context. */
|
/* Fill section context. */
|
||||||
out->fs_contexts[i].nca_ctx = out;
|
fs_ctx->nca_ctx = out;
|
||||||
out->fs_contexts[i].section_num = i;
|
fs_ctx->section_num = i;
|
||||||
out->fs_contexts[i].section_type = NcaFsSectionType_Invalid; /* Placeholder. */
|
fs_ctx->section_type = NcaFsSectionType_Invalid; /* Placeholder. */
|
||||||
|
|
||||||
/* Don't proceed if this NCA FS section isn't populated. */
|
/* Don't proceed if this NCA FS section isn't populated. */
|
||||||
if (!ncaIsFsInfoEntryValid(&(out->header.fs_info[i]))) continue;
|
if (!ncaIsFsInfoEntryValid(fs_info)) continue;
|
||||||
|
|
||||||
/* Calculate section offset and size. */
|
/* Calculate section offset and size. */
|
||||||
out->fs_contexts[i].section_offset = NCA_FS_SECTOR_OFFSET(out->header.fs_info[i].start_sector);
|
fs_ctx->section_offset = NCA_FS_SECTOR_OFFSET(fs_info->start_sector);
|
||||||
out->fs_contexts[i].section_size = (NCA_FS_SECTOR_OFFSET(out->header.fs_info[i].end_sector) - out->fs_contexts[i].section_offset);
|
fs_ctx->section_size = (NCA_FS_SECTOR_OFFSET(fs_info->end_sector) - fs_ctx->section_offset);
|
||||||
|
|
||||||
/* Check if we're dealing with an invalid offset/size. */
|
/* Check if we're dealing with an invalid offset/size. */
|
||||||
if (out->fs_contexts[i].section_offset < sizeof(NcaHeader) || !out->fs_contexts[i].section_size || \
|
if (fs_ctx->section_offset < sizeof(NcaHeader) || !fs_ctx->section_size || \
|
||||||
(out->fs_contexts[i].section_offset + out->fs_contexts[i].section_size) > out->content_size) continue;
|
(fs_ctx->section_offset + fs_ctx->section_size) > out->content_size) continue;
|
||||||
|
|
||||||
/* Determine encryption type. */
|
/* Determine encryption type. */
|
||||||
out->fs_contexts[i].encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_AesXts : out->fs_contexts[i].header.encryption_type);
|
fs_ctx->encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_AesXts : fs_ctx->header.encryption_type);
|
||||||
if (out->fs_contexts[i].encryption_type == NcaEncryptionType_Auto)
|
if (fs_ctx->encryption_type == NcaEncryptionType_Auto)
|
||||||
{
|
{
|
||||||
switch(out->fs_contexts[i].section_num)
|
switch(fs_ctx->section_num)
|
||||||
{
|
{
|
||||||
case 0: /* ExeFS Partition FS. */
|
case 0: /* ExeFS Partition FS. */
|
||||||
case 1: /* RomFS. */
|
case 1: /* RomFS. */
|
||||||
out->fs_contexts[i].encryption_type = NcaEncryptionType_AesCtr;
|
fs_ctx->encryption_type = NcaEncryptionType_AesCtr;
|
||||||
break;
|
break;
|
||||||
case 2: /* Logo Partition FS. */
|
case 2: /* Logo Partition FS. */
|
||||||
out->fs_contexts[i].encryption_type = NcaEncryptionType_None;
|
fs_ctx->encryption_type = NcaEncryptionType_None;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -198,53 +191,53 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if we're dealing with an invalid encryption type value. */
|
/* Check if we're dealing with an invalid encryption type value. */
|
||||||
if (out->fs_contexts[i].encryption_type == NcaEncryptionType_Auto || out->fs_contexts[i].encryption_type > NcaEncryptionType_AesCtrEx) continue;
|
if (fs_ctx->encryption_type == NcaEncryptionType_Auto || fs_ctx->encryption_type > NcaEncryptionType_AesCtrEx) continue;
|
||||||
|
|
||||||
/* Determine FS section type. */
|
/* Determine FS section type. */
|
||||||
if (out->fs_contexts[i].header.fs_type == NcaFsType_PartitionFs && out->fs_contexts[i].header.hash_type == NcaHashType_HierarchicalSha256)
|
if (fs_ctx->header.fs_type == NcaFsType_PartitionFs && fs_ctx->header.hash_type == NcaHashType_HierarchicalSha256)
|
||||||
{
|
{
|
||||||
out->fs_contexts[i].section_type = NcaFsSectionType_PartitionFs;
|
fs_ctx->section_type = NcaFsSectionType_PartitionFs;
|
||||||
} else
|
} else
|
||||||
if (out->fs_contexts[i].header.fs_type == NcaFsType_RomFs && out->fs_contexts[i].header.hash_type == NcaHashType_HierarchicalIntegrity)
|
if (fs_ctx->header.fs_type == NcaFsType_RomFs && fs_ctx->header.hash_type == NcaHashType_HierarchicalIntegrity)
|
||||||
{
|
{
|
||||||
out->fs_contexts[i].section_type = (out->fs_contexts[i].encryption_type == NcaEncryptionType_AesCtrEx ? NcaFsSectionType_PatchRomFs : NcaFsSectionType_RomFs);
|
fs_ctx->section_type = (fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx ? NcaFsSectionType_PatchRomFs : NcaFsSectionType_RomFs);
|
||||||
} else
|
} else
|
||||||
if (out->fs_contexts[i].header.fs_type == NcaFsType_RomFs && out->fs_contexts[i].header.hash_type == NcaHashType_HierarchicalSha256 && out->format_version == NcaVersion_Nca0)
|
if (fs_ctx->header.fs_type == NcaFsType_RomFs && fs_ctx->header.hash_type == NcaHashType_HierarchicalSha256 && out->format_version == NcaVersion_Nca0)
|
||||||
{
|
{
|
||||||
out->fs_contexts[i].section_type = NcaFsSectionType_Nca0RomFs;
|
fs_ctx->section_type = NcaFsSectionType_Nca0RomFs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if we're dealing with an invalid section type value. */
|
/* Check if we're dealing with an invalid section type value. */
|
||||||
if (out->fs_contexts[i].section_type >= NcaFsSectionType_Invalid) continue;
|
if (fs_ctx->section_type >= NcaFsSectionType_Invalid) continue;
|
||||||
|
|
||||||
/* Initialize crypto data. */
|
/* Initialize crypto data. */
|
||||||
if ((!out->rights_id_available || (out->rights_id_available && out->titlekey_retrieved)) && out->fs_contexts[i].encryption_type > NcaEncryptionType_None && \
|
if ((!out->rights_id_available || (out->rights_id_available && out->titlekey_retrieved)) && fs_ctx->encryption_type > NcaEncryptionType_None && \
|
||||||
out->fs_contexts[i].encryption_type <= NcaEncryptionType_AesCtrEx)
|
fs_ctx->encryption_type <= NcaEncryptionType_AesCtrEx)
|
||||||
{
|
{
|
||||||
/* Initialize section CTR. */
|
/* Initialize section CTR. */
|
||||||
ncaInitializeAesCtrIv(out->fs_contexts[i].ctr, out->fs_contexts[i].header.aes_ctr_upper_iv.value, out->fs_contexts[i].section_offset);
|
ncaInitializeAesCtrIv(fs_ctx->ctr, fs_ctx->header.aes_ctr_upper_iv.value, fs_ctx->section_offset);
|
||||||
|
|
||||||
/* Initialize AES context. */
|
/* Initialize AES context. */
|
||||||
if (out->rights_id_available)
|
if (out->rights_id_available)
|
||||||
{
|
{
|
||||||
/* AES-128-CTR is always used for FS crypto in NCAs with a rights ID. */
|
/* AES-128-CTR is always used for FS crypto in NCAs with a rights ID. */
|
||||||
aes128CtrContextCreate(&(out->fs_contexts[i].ctr_ctx), out->titlekey, out->fs_contexts[i].ctr);
|
aes128CtrContextCreate(&(fs_ctx->ctr_ctx), out->titlekey, fs_ctx->ctr);
|
||||||
} else {
|
} else {
|
||||||
if (out->fs_contexts[i].encryption_type == NcaEncryptionType_AesXts)
|
if (fs_ctx->encryption_type == NcaEncryptionType_AesXts)
|
||||||
{
|
{
|
||||||
/* We need to create two different contexts: one for decryption and another one for encryption. */
|
/* We need to create two different contexts: one for decryption and another one for encryption. */
|
||||||
aes128XtsContextCreate(&(out->fs_contexts[i].xts_decrypt_ctx), out->decrypted_key_area.aes_xts_1, out->decrypted_key_area.aes_xts_2, false);
|
aes128XtsContextCreate(&(fs_ctx->xts_decrypt_ctx), out->decrypted_key_area.aes_xts_1, out->decrypted_key_area.aes_xts_2, false);
|
||||||
aes128XtsContextCreate(&(out->fs_contexts[i].xts_encrypt_ctx), out->decrypted_key_area.aes_xts_1, out->decrypted_key_area.aes_xts_2, true);
|
aes128XtsContextCreate(&(fs_ctx->xts_encrypt_ctx), out->decrypted_key_area.aes_xts_1, out->decrypted_key_area.aes_xts_2, true);
|
||||||
} else
|
} else
|
||||||
if (out->fs_contexts[i].encryption_type == NcaEncryptionType_AesCtr || out->fs_contexts[i].encryption_type == NcaEncryptionType_AesCtrEx)
|
if (fs_ctx->encryption_type == NcaEncryptionType_AesCtr || fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx)
|
||||||
{
|
{
|
||||||
aes128CtrContextCreate(&(out->fs_contexts[i].ctr_ctx), out->decrypted_key_area.aes_ctr, out->fs_contexts[i].ctr);
|
aes128CtrContextCreate(&(fs_ctx->ctr_ctx), out->decrypted_key_area.aes_ctr, fs_ctx->ctr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Enable FS context if we got up to this point. */
|
/* Enable FS context if we got up to this point. */
|
||||||
out->fs_contexts[i].enabled = true;
|
fs_ctx->enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -356,8 +349,10 @@ void ncaRemoveTitlekeyCrypto(NcaContext *ctx)
|
||||||
for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
||||||
{
|
{
|
||||||
/* AES-128-XTS is not used in FS sections from NCAs with titlekey crypto. */
|
/* AES-128-XTS is not used in FS sections from NCAs with titlekey crypto. */
|
||||||
if (!ctx->fs_contexts[i].enabled || (ctx->fs_contexts[i].encryption_type != NcaEncryptionType_AesCtr && ctx->fs_contexts[i].encryption_type != NcaEncryptionType_AesCtrEx)) continue;
|
NcaFsSectionContext *fs_ctx = &(ctx->fs_ctx[i]);
|
||||||
u8 *key_ptr = (ctx->fs_contexts[i].encryption_type == NcaEncryptionType_AesCtr ? ctx->decrypted_key_area.aes_ctr : ctx->decrypted_key_area.aes_ctr_ex);
|
if (!fs_ctx->enabled || (fs_ctx->encryption_type != NcaEncryptionType_AesCtr && fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx)) continue;
|
||||||
|
|
||||||
|
u8 *key_ptr = (fs_ctx->encryption_type == NcaEncryptionType_AesCtr ? ctx->decrypted_key_area.aes_ctr : ctx->decrypted_key_area.aes_ctr_ex);
|
||||||
memcpy(key_ptr, ctx->titlekey, AES_128_KEY_SIZE);
|
memcpy(key_ptr, ctx->titlekey, AES_128_KEY_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,7 +390,7 @@ bool ncaEncryptHeader(NcaContext *ctx)
|
||||||
if (ctx->format_version == NcaVersion_Nca0) aes128XtsContextCreate(&nca0_fs_header_ctx, ctx->decrypted_key_area.aes_xts_1, ctx->decrypted_key_area.aes_xts_2, true);
|
if (ctx->format_version == NcaVersion_Nca0) aes128XtsContextCreate(&nca0_fs_header_ctx, ctx->decrypted_key_area.aes_xts_1, ctx->decrypted_key_area.aes_xts_2, true);
|
||||||
|
|
||||||
/* Encrypt NCA header. */
|
/* Encrypt NCA header. */
|
||||||
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), sizeof(NcaHeader), 0, NCA_AES_XTS_SECTOR_SIZE, true);
|
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->encrypted_header), &(ctx->header), sizeof(NcaHeader), 0, NCA_AES_XTS_SECTOR_SIZE, true);
|
||||||
if (crypt_res != sizeof(NcaHeader))
|
if (crypt_res != sizeof(NcaHeader))
|
||||||
{
|
{
|
||||||
LOGFILE("Error encrypting NCA \"%s\" header!", ctx->content_id_str);
|
LOGFILE("Error encrypting NCA \"%s\" header!", ctx->content_id_str);
|
||||||
|
@ -404,20 +399,22 @@ bool ncaEncryptHeader(NcaContext *ctx)
|
||||||
|
|
||||||
/* Encrypt NCA FS section headers. */
|
/* Encrypt NCA FS section headers. */
|
||||||
/* Both NCA2 and NCA3 place the NCA FS section headers right after the NCA header. However, NCA0 places them at the start sector from each NCA FS section. */
|
/* Both NCA2 and NCA3 place the NCA FS section headers right after the NCA header. However, NCA0 places them at the start sector from each NCA FS section. */
|
||||||
/* NCA0 FS section headers will be encrypted in-place, but they need to be written to their proper offsets. */
|
|
||||||
for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
||||||
{
|
{
|
||||||
|
NcaFsInfo *fs_info = &(ctx->header.fs_info[i]);
|
||||||
|
NcaFsSectionContext *fs_ctx = &(ctx->fs_ctx[i]);
|
||||||
|
|
||||||
/* Don't proceed if this NCA FS section isn't populated. */
|
/* Don't proceed if this NCA FS section isn't populated. */
|
||||||
if (ctx->format_version != NcaVersion_Nca3 && !ncaIsFsInfoEntryValid(&(ctx->header.fs_info[i]))) continue;
|
if (ctx->format_version != NcaVersion_Nca3 && !ncaIsFsInfoEntryValid(fs_info)) continue;
|
||||||
|
|
||||||
/* The AES-XTS sector number for each NCA FS header varies depending on the NCA format version. */
|
/* The AES-XTS sector number for each NCA FS header varies depending on the NCA format version. */
|
||||||
/* NCA3 uses sector number 0 for the NCA header, then increases it with each new sector (e.g. making the first NCA FS section header use sector number 2, and so on). */
|
/* NCA3 uses sector number 0 for the NCA header, then increases it with each new sector (e.g. making the first NCA FS section header use sector number 2, and so on). */
|
||||||
/* NCA2 uses sector number 0 for each NCA FS section header. */
|
/* NCA2 uses sector number 0 for each NCA FS section header. */
|
||||||
/* NCA0 uses sector number 0 for the NCA header, then uses sector number 0 for the rest of the data and increases it with each new sector. */
|
/* NCA0 uses sector number 0 for the NCA header, then uses sector number 0 for the rest of the data and increases it with each new sector. */
|
||||||
Aes128XtsContext *aes_xts_ctx = (ctx->format_version != NcaVersion_Nca0 ? &hdr_aes_ctx : &nca0_fs_header_ctx);
|
Aes128XtsContext *aes_xts_ctx = (ctx->format_version != NcaVersion_Nca0 ? &hdr_aes_ctx : &nca0_fs_header_ctx);
|
||||||
u64 sector = (ctx->format_version == NcaVersion_Nca3 ? (2U + i) : (ctx->format_version == NcaVersion_Nca2 ? 0 : (ctx->header.fs_info[i].start_sector - 2)));
|
u64 sector = (ctx->format_version == NcaVersion_Nca3 ? (2U + i) : (ctx->format_version == NcaVersion_Nca2 ? 0 : (fs_info->start_sector - 2)));
|
||||||
|
|
||||||
crypt_res = aes128XtsNintendoCrypt(aes_xts_ctx, &(ctx->fs_contexts[i].header), &(ctx->fs_contexts[i].header), sizeof(NcaFsHeader), sector, NCA_AES_XTS_SECTOR_SIZE, true);
|
crypt_res = aes128XtsNintendoCrypt(aes_xts_ctx, &(fs_ctx->encrypted_header), &(fs_ctx->header), sizeof(NcaFsHeader), sector, NCA_AES_XTS_SECTOR_SIZE, true);
|
||||||
if (crypt_res != sizeof(NcaFsHeader))
|
if (crypt_res != sizeof(NcaFsHeader))
|
||||||
{
|
{
|
||||||
LOGFILE("Error encrypting NCA%u \"%s\" FS section header #%u!", ctx->format_version, ctx->content_id_str, i);
|
LOGFILE("Error encrypting NCA%u \"%s\" FS section header #%u!", ctx->format_version, ctx->content_id_str, i);
|
||||||
|
@ -446,7 +443,7 @@ NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info)
|
||||||
return (memcmp(&tmp_fs_info, fs_info, sizeof(NcaFsInfo)) != 0);
|
return (memcmp(&tmp_fs_info, fs_info, sizeof(NcaFsInfo)) != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool ncaDecryptHeader(NcaContext *ctx)
|
static bool ncaReadDecryptedHeader(NcaContext *ctx)
|
||||||
{
|
{
|
||||||
if (!ctx || !strlen(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH)
|
if (!ctx || !strlen(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH)
|
||||||
{
|
{
|
||||||
|
@ -459,11 +456,18 @@ static bool ncaDecryptHeader(NcaContext *ctx)
|
||||||
const u8 *header_key = keysGetNcaHeaderKey();
|
const u8 *header_key = keysGetNcaHeaderKey();
|
||||||
Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0};
|
Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0};
|
||||||
|
|
||||||
|
/* Read NCA header. */
|
||||||
|
if (!ncaReadContentFile(ctx, &(ctx->encrypted_header), sizeof(NcaHeader), 0))
|
||||||
|
{
|
||||||
|
LOGFILE("Failed to read NCA \"%s\" header!", ctx->content_id_str);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/* Prepare NCA header AES-128-XTS context. */
|
/* Prepare NCA header AES-128-XTS context. */
|
||||||
aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + AES_128_KEY_SIZE, false);
|
aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + AES_128_KEY_SIZE, false);
|
||||||
|
|
||||||
/* Decrypt NCA header. */
|
/* Decrypt NCA header. */
|
||||||
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), sizeof(NcaHeader), 0, NCA_AES_XTS_SECTOR_SIZE, false);
|
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->encrypted_header), sizeof(NcaHeader), 0, NCA_AES_XTS_SECTOR_SIZE, false);
|
||||||
magic = __builtin_bswap32(ctx->header.magic);
|
magic = __builtin_bswap32(ctx->header.magic);
|
||||||
|
|
||||||
if (crypt_res != sizeof(NcaHeader) || (magic != NCA_NCA3_MAGIC && magic != NCA_NCA2_MAGIC && magic != NCA_NCA0_MAGIC) || ctx->header.content_size != ctx->content_size)
|
if (crypt_res != sizeof(NcaHeader) || (magic != NCA_NCA3_MAGIC && magic != NCA_NCA2_MAGIC && magic != NCA_NCA0_MAGIC) || ctx->header.content_size != ctx->content_size)
|
||||||
|
@ -476,6 +480,7 @@ static bool ncaDecryptHeader(NcaContext *ctx)
|
||||||
ctx->format_version = (magic == NCA_NCA3_MAGIC ? NcaVersion_Nca3 : (magic == NCA_NCA2_MAGIC ? NcaVersion_Nca2 : NcaVersion_Nca0));
|
ctx->format_version = (magic == NCA_NCA3_MAGIC ? NcaVersion_Nca3 : (magic == NCA_NCA2_MAGIC ? NcaVersion_Nca2 : NcaVersion_Nca0));
|
||||||
ctx->key_generation = ncaGetKeyGenerationValue(ctx);
|
ctx->key_generation = ncaGetKeyGenerationValue(ctx);
|
||||||
ctx->rights_id_available = ncaCheckRightsIdAvailability(ctx);
|
ctx->rights_id_available = ncaCheckRightsIdAvailability(ctx);
|
||||||
|
sha256CalculateHash(ctx->header_hash, &(ctx->header), sizeof(NcaHeader));
|
||||||
|
|
||||||
/* Decrypt NCA key area (if needed). */
|
/* Decrypt NCA key area (if needed). */
|
||||||
if (!ctx->rights_id_available && !ncaDecryptKeyArea(ctx))
|
if (!ctx->rights_id_available && !ncaDecryptKeyArea(ctx))
|
||||||
|
@ -491,12 +496,15 @@ static bool ncaDecryptHeader(NcaContext *ctx)
|
||||||
/* Both NCA2 and NCA3 place the NCA FS section headers right after the NCA header. However, NCA0 places them at the start sector from each NCA FS section. */
|
/* Both NCA2 and NCA3 place the NCA FS section headers right after the NCA header. However, NCA0 places them at the start sector from each NCA FS section. */
|
||||||
for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
||||||
{
|
{
|
||||||
|
NcaFsInfo *fs_info = &(ctx->header.fs_info[i]);
|
||||||
|
NcaFsSectionContext *fs_ctx = &(ctx->fs_ctx[i]);
|
||||||
|
|
||||||
/* Don't proceed if this NCA FS section isn't populated. */
|
/* Don't proceed if this NCA FS section isn't populated. */
|
||||||
if (ctx->format_version != NcaVersion_Nca3 && !ncaIsFsInfoEntryValid(&(ctx->header.fs_info[i]))) continue;
|
if (ctx->format_version != NcaVersion_Nca3 && !ncaIsFsInfoEntryValid(fs_info)) continue;
|
||||||
|
|
||||||
/* Read NCA FS section header. */
|
/* Read NCA FS section header. */
|
||||||
u64 fs_header_offset = (ctx->format_version != NcaVersion_Nca0 ? (sizeof(NcaHeader) + (i * sizeof(NcaFsHeader))) : NCA_FS_SECTOR_OFFSET(ctx->header.fs_info[i].start_sector));
|
u64 fs_header_offset = (ctx->format_version != NcaVersion_Nca0 ? (sizeof(NcaHeader) + (i * sizeof(NcaFsHeader))) : NCA_FS_SECTOR_OFFSET(fs_info->start_sector));
|
||||||
if (!ncaReadContentFile(ctx, &(ctx->fs_contexts[i].header), sizeof(NcaFsHeader), fs_header_offset))
|
if (!ncaReadContentFile(ctx, &(fs_ctx->encrypted_header), sizeof(NcaFsHeader), fs_header_offset))
|
||||||
{
|
{
|
||||||
LOGFILE("Failed to read NCA%u \"%s\" FS section header #%u at offset 0x%lX!", ctx->format_version, ctx->content_id_str, i, fs_header_offset);
|
LOGFILE("Failed to read NCA%u \"%s\" FS section header #%u at offset 0x%lX!", ctx->format_version, ctx->content_id_str, i, fs_header_offset);
|
||||||
return false;
|
return false;
|
||||||
|
@ -507,9 +515,9 @@ static bool ncaDecryptHeader(NcaContext *ctx)
|
||||||
/* NCA2 uses sector number 0 for each NCA FS section header. */
|
/* NCA2 uses sector number 0 for each NCA FS section header. */
|
||||||
/* NCA0 uses sector number 0 for the NCA header, then uses sector number 0 for the rest of the data and increases it with each new sector. */
|
/* NCA0 uses sector number 0 for the NCA header, then uses sector number 0 for the rest of the data and increases it with each new sector. */
|
||||||
Aes128XtsContext *aes_xts_ctx = (ctx->format_version != NcaVersion_Nca0 ? &hdr_aes_ctx : &nca0_fs_header_ctx);
|
Aes128XtsContext *aes_xts_ctx = (ctx->format_version != NcaVersion_Nca0 ? &hdr_aes_ctx : &nca0_fs_header_ctx);
|
||||||
u64 sector = (ctx->format_version == NcaVersion_Nca3 ? (2U + i) : (ctx->format_version == NcaVersion_Nca2 ? 0 : (ctx->header.fs_info[i].start_sector - 2)));
|
u64 sector = (ctx->format_version == NcaVersion_Nca3 ? (2U + i) : (ctx->format_version == NcaVersion_Nca2 ? 0 : (fs_info->start_sector - 2)));
|
||||||
|
|
||||||
crypt_res = aes128XtsNintendoCrypt(aes_xts_ctx, &(ctx->fs_contexts[i].header), &(ctx->fs_contexts[i].header), sizeof(NcaFsHeader), sector, NCA_AES_XTS_SECTOR_SIZE, false);
|
crypt_res = aes128XtsNintendoCrypt(aes_xts_ctx, &(fs_ctx->header), &(fs_ctx->encrypted_header), sizeof(NcaFsHeader), sector, NCA_AES_XTS_SECTOR_SIZE, false);
|
||||||
if (crypt_res != sizeof(NcaFsHeader))
|
if (crypt_res != sizeof(NcaFsHeader))
|
||||||
{
|
{
|
||||||
LOGFILE("Error decrypting NCA%u \"%s\" FS section header #%u!", ctx->format_version, ctx->content_id_str, i);
|
LOGFILE("Error decrypting NCA%u \"%s\" FS section header #%u!", ctx->format_version, ctx->content_id_str, i);
|
||||||
|
|
44
source/nca.h
44
source/nca.h
|
@ -270,10 +270,14 @@ typedef enum {
|
||||||
NcaFsSectionType_Invalid = 4
|
NcaFsSectionType_Invalid = 4
|
||||||
} NcaFsSectionType;
|
} NcaFsSectionType;
|
||||||
|
|
||||||
|
/// Unlike NCA contexts, we don't need to keep a hash for the NCA FS section header in NCA FS section contexts.
|
||||||
|
/// This is because the functions that modify the NCA FS section header also update the NCA FS section header hash stored in the NCA header.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool enabled;
|
bool enabled;
|
||||||
void *nca_ctx; ///< NcaContext. Used to perform NCA reads.
|
void *nca_ctx; ///< NcaContext. Used to perform NCA reads.
|
||||||
NcaFsHeader header; ///< NCA FS section header.
|
NcaFsHeader header; ///< Plaintext NCA FS section header.
|
||||||
|
NcaFsHeader encrypted_header; ///< Encrypted NCA FS section header. If the plaintext NCA FS section header is modified, this will hold an encrypted copy of it.
|
||||||
|
///< Otherwise, this holds the unmodified, encrypted NCA FS section header.
|
||||||
u8 section_num;
|
u8 section_num;
|
||||||
u64 section_offset;
|
u64 section_offset;
|
||||||
u64 section_size;
|
u64 section_size;
|
||||||
|
@ -299,26 +303,28 @@ typedef struct {
|
||||||
} NcaDecryptedKeyArea;
|
} NcaDecryptedKeyArea;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u8 storage_id; ///< NcmStorageId.
|
u8 storage_id; ///< NcmStorageId.
|
||||||
NcmContentStorage *ncm_storage; ///< Pointer to a NcmContentStorage instance. Used to read NCA data from eMMC/SD.
|
NcmContentStorage *ncm_storage; ///< Pointer to a NcmContentStorage instance. Used to read NCA data from eMMC/SD.
|
||||||
u64 gamecard_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard.
|
u64 gamecard_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard.
|
||||||
NcmContentId content_id; ///< Also used to read NCA data.
|
NcmContentId content_id; ///< Also used to read NCA data.
|
||||||
char content_id_str[0x21];
|
char content_id_str[0x21];
|
||||||
u8 hash[SHA256_HASH_SIZE]; ///< Manually calculated (if needed).
|
u8 hash[SHA256_HASH_SIZE]; ///< Manually calculated (if needed).
|
||||||
char hash_str[0x41];
|
char hash_str[0x41];
|
||||||
u8 format_version; ///< NcaVersion.
|
u8 format_version; ///< NcaVersion.
|
||||||
u8 content_type; ///< NcmContentType. Retrieved from NcmContentInfo.
|
u8 content_type; ///< NcmContentType. Retrieved from NcmContentInfo.
|
||||||
u64 content_size; ///< Retrieved from NcmContentInfo.
|
u64 content_size; ///< Retrieved from NcmContentInfo.
|
||||||
u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header.
|
u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header.
|
||||||
u8 id_offset; ///< Retrieved from NcmContentInfo.
|
u8 id_offset; ///< Retrieved from NcmContentInfo.
|
||||||
bool rights_id_available;
|
bool rights_id_available;
|
||||||
bool titlekey_retrieved;
|
bool titlekey_retrieved;
|
||||||
u8 titlekey[AES_128_KEY_SIZE]; ///< Decrypted titlekey from the ticket.
|
u8 titlekey[AES_128_KEY_SIZE]; ///< Decrypted titlekey from the ticket.
|
||||||
NcaHeader header; ///< NCA header.
|
NcaHeader header; ///< Plaintext NCA header.
|
||||||
u8 header_hash[SHA256_HASH_SIZE]; ///< NCA header hash. Used to determine if it's necessary to replace the NCA header while dumping this NCA.
|
u8 header_hash[SHA256_HASH_SIZE]; ///< Plaintext NCA header hash. Used to determine if it's necessary to replace the NCA header while dumping this NCA.
|
||||||
NcaFsSectionContext fs_contexts[NCA_FS_HEADER_COUNT];
|
NcaHeader encrypted_header; ///< Encrypted NCA header. If the plaintext NCA header is modified, this will hold an encrypted copy of it.
|
||||||
|
///< Otherwise, this holds the unmodified, encrypted NCA header.
|
||||||
|
NcaFsSectionContext fs_ctx[NCA_FS_HEADER_COUNT];
|
||||||
NcaDecryptedKeyArea decrypted_key_area;
|
NcaDecryptedKeyArea decrypted_key_area;
|
||||||
void *content_type_ctx; ///< Pointer to a content type context (e.g. ContentMetaContext, ProgramInfoContext, NacpContext, LegalInfoContext). Set to NULL if unused.
|
void *content_type_ctx; ///< Pointer to a content type context (e.g. ContentMetaContext, ProgramInfoContext, NacpContext, LegalInfoContext). Set to NULL if unused.
|
||||||
} NcaContext;
|
} NcaContext;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -362,11 +368,12 @@ bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 of
|
||||||
/// Input offset must be relative to the start of the NCA FS section.
|
/// Input offset must be relative to the start of the NCA FS section.
|
||||||
bool ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val);
|
bool ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val);
|
||||||
|
|
||||||
/// Returns a pointer to a heap-allocated buffer used to encrypt the input plaintext data, based on the encryption type used by the input NCA FS section, as well as its offset and size.
|
/// Returns a pointer to a dynamically allocated buffer used to encrypt the input plaintext data, based on the encryption type used by the input NCA FS section, as well as its offset and size.
|
||||||
/// Input offset must be relative to the start of the NCA FS section.
|
/// 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 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 seamlessly replace data while dumping a NCA.
|
/// Output offset is relative to the start of the NCA content file, making it easier to use the output encrypted block to seamlessly replace data while dumping a NCA.
|
||||||
/// This function isn't compatible with Patch RomFS sections.
|
/// This function isn't compatible with Patch RomFS sections.
|
||||||
|
/// Used internally by both ncaGenerateHierarchicalSha256Patch() and ncaGenerateHierarchicalIntegrityPatch().
|
||||||
void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const 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);
|
||||||
|
|
||||||
/// Generates HierarchicalSha256 FS section patch data, which can be used to seamlessly replace NCA data.
|
/// Generates HierarchicalSha256 FS section patch data, which can be used to seamlessly replace NCA data.
|
||||||
|
@ -403,7 +410,8 @@ const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx);
|
||||||
/// Removes titlekey crypto dependency from a NCA context by wiping the Rights ID from the underlying NCA header and copying the decrypted titlekey to the NCA key area.
|
/// Removes titlekey crypto dependency from a NCA context by wiping the Rights ID from the underlying NCA header and copying the decrypted titlekey to the NCA key area.
|
||||||
void ncaRemoveTitlekeyCrypto(NcaContext *ctx);
|
void ncaRemoveTitlekeyCrypto(NcaContext *ctx);
|
||||||
|
|
||||||
/// Encrypts NCA header and NCA FS headers from a NCA context.
|
/// Encrypts NCA header and NCA FS headers.
|
||||||
|
/// The 'encrypted_header' member from the NCA context and its underlying NCA FS section contexts is updated by this function.
|
||||||
bool ncaEncryptHeader(NcaContext *ctx);
|
bool ncaEncryptHeader(NcaContext *ctx);
|
||||||
|
|
||||||
/// Updates the content ID and hash from a NCA context using a provided SHA-256 checksum.
|
/// Updates the content ID and hash from a NCA context using a provided SHA-256 checksum.
|
||||||
|
|
|
@ -156,14 +156,14 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out->acid_header->fs_access_control_offset < sizeof(NpdmAcidHeader) || out->acid_header->fs_access_control_size < sizeof(NpdmAcidFsAccessControlDescriptor) || \
|
if (out->acid_header->fs_access_control_offset < sizeof(NpdmAcidHeader) || out->acid_header->fs_access_control_size < sizeof(NpdmFsAccessControlDescriptor) || \
|
||||||
(out->acid_header->fs_access_control_offset + out->acid_header->fs_access_control_size) > out->meta_header->acid_size)
|
(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);
|
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;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
out->acid_fac_descriptor = (NpdmAcidFsAccessControlDescriptor*)(out->raw_data + out->meta_header->acid_offset + out->acid_header->fs_access_control_offset);
|
out->acid_fac_descriptor = (NpdmFsAccessControlDescriptor*)(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_size)
|
||||||
{
|
{
|
||||||
|
@ -212,14 +212,14 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out->aci_header->fs_access_control_offset < sizeof(NpdmAciHeader) || out->aci_header->fs_access_control_size < sizeof(NpdmAciFsAccessControlDescriptor) || \
|
if (out->aci_header->fs_access_control_offset < sizeof(NpdmAciHeader) || out->aci_header->fs_access_control_size < sizeof(NpdmFsAccessControlData) || \
|
||||||
(out->aci_header->fs_access_control_offset + out->aci_header->fs_access_control_size) > out->meta_header->aci_size)
|
(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);
|
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;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
out->aci_fac_descriptor = (NpdmAciFsAccessControlDescriptor*)(out->raw_data + out->meta_header->aci_offset + out->aci_header->fs_access_control_offset);
|
out->aci_fac_data = (NpdmFsAccessControlData*)(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_size)
|
||||||
{
|
{
|
||||||
|
|
|
@ -51,23 +51,23 @@ typedef struct {
|
||||||
/// This is the start of every NPDM file.
|
/// This is the start of every NPDM file.
|
||||||
/// This is followed by ACID and ACI0 sections, both with variable offsets and sizes.
|
/// This is followed by ACID and ACI0 sections, both with variable offsets and sizes.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u32 magic; ///< "NPDM".
|
u32 magic; ///< "NPDM".
|
||||||
u8 acid_signature_key_generation;
|
u8 acid_signature_key_generation;
|
||||||
u8 reserved_1[0x7];
|
u8 reserved_1[0x7];
|
||||||
NpdmMetaFlags flags;
|
NpdmMetaFlags flags;
|
||||||
u8 reserved_2;
|
u8 reserved_2;
|
||||||
u8 main_thread_priority; ///< Must not exceed NPDM_MAIN_THREAD_MAX_PRIORITY.
|
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 main_thread_core_number; ///< Must not exceed NPDM_MAIN_THREAD_MAX_CORE_NUMBER.
|
||||||
u8 reserved_3[0x4];
|
u8 reserved_3[0x4];
|
||||||
u32 system_resource_size; ///< Must not exceed NPDM_SYSTEM_RESOURCE_MAX_SIZE.
|
u32 system_resource_size; ///< Must not exceed NPDM_SYSTEM_RESOURCE_MAX_SIZE.
|
||||||
VersionType1 version;
|
VersionType1 version;
|
||||||
u32 main_thread_stack_size; ///< Must be aligned to NPDM_MAIN_THREAD_STACK_SIZE_ALIGNMENT.
|
u32 main_thread_stack_size; ///< Must be aligned to NPDM_MAIN_THREAD_STACK_SIZE_ALIGNMENT.
|
||||||
char name[0x10]; ///< Usually set to "Application".
|
char name[0x10]; ///< Usually set to "Application".
|
||||||
char product_code[0x10]; ///< Usually zeroed out.
|
char product_code[0x10]; ///< Usually zeroed out.
|
||||||
u8 reserved_4[0x30];
|
u8 reserved_4[0x30];
|
||||||
u32 aci_offset; ///< Offset value relative to the start of this header.
|
u32 aci_offset; ///< Offset value relative to the start of this header.
|
||||||
u32 aci_size;
|
u32 aci_size;
|
||||||
u32 acid_offset; ///< Offset value relative to the start of this header.
|
u32 acid_offset; ///< Offset value relative to the start of this header.
|
||||||
u32 acid_size;
|
u32 acid_size;
|
||||||
} NpdmMetaHeader;
|
} NpdmMetaHeader;
|
||||||
|
|
||||||
|
@ -90,27 +90,27 @@ typedef struct {
|
||||||
} NpdmAcidFlags;
|
} NpdmAcidFlags;
|
||||||
|
|
||||||
/// This is the start of an ACID section.
|
/// This is the start of an ACID section.
|
||||||
/// This is followed by FsAccessControl (ACID), SrvAccessControl and KernelCapability descriptors, each one aligned to a 0x10 byte boundary using zero padding (if needed).
|
/// This is followed by FsAccessControl, SrvAccessControl and KernelCapability descriptors, each one aligned to a 0x10 byte boundary using zero padding (if needed).
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u8 signature[0x100]; ///< RSA-2048-PSS with SHA-256 signature over the rest of the ACID section, using the value from the 'size' member.
|
u8 signature[0x100]; ///< RSA-2048-PSS with SHA-256 signature over the rest of the ACID section, using the value from the 'size' member.
|
||||||
u8 public_key[0x100]; ///< RSA public key used to verify the ACID signature from the Program NCA header.
|
u8 public_key[0x100]; ///< RSA public key used to verify the ACID signature from the Program NCA header.
|
||||||
u32 magic; ///< "ACID".
|
u32 magic; ///< "ACID".
|
||||||
u32 size; ///< Must be equal to ACID section size from the META header minus 0x100 (ACID signature size).
|
u32 size; ///< Must be equal to ACID section size from the META header minus 0x100 (ACID signature size).
|
||||||
u8 reserved_1[0x4];
|
u8 reserved_1[0x4];
|
||||||
NpdmAcidFlags flags;
|
NpdmAcidFlags flags;
|
||||||
u64 program_id_min;
|
u64 program_id_min;
|
||||||
u64 program_id_max;
|
u64 program_id_max;
|
||||||
u32 fs_access_control_offset; ///< Offset value relative to the start of this header.
|
u32 fs_access_control_offset; ///< Offset value relative to the start of this header.
|
||||||
u32 fs_access_control_size;
|
u32 fs_access_control_size;
|
||||||
u32 srv_access_control_offset; ///< Offset value relative to the start of this header.
|
u32 srv_access_control_offset; ///< Offset value relative to the start of this header.
|
||||||
u32 srv_access_control_size;
|
u32 srv_access_control_size;
|
||||||
u32 kernel_capability_offset; ///< Offset value relative to the start of this header.
|
u32 kernel_capability_offset; ///< Offset value relative to the start of this header.
|
||||||
u32 kernel_capability_size;
|
u32 kernel_capability_size;
|
||||||
u8 reserved_2[0x8];
|
u8 reserved_2[0x8];
|
||||||
} NpdmAcidHeader;
|
} NpdmAcidHeader;
|
||||||
|
|
||||||
/// This is the start of an ACI0 section.
|
/// This is the start of an ACI0 section.
|
||||||
/// This is followed by FsAccessControl (ACI0), SrvAccessControl and KernelCapability descriptors, each one aligned to a 0x10 byte boundary using zero padding (if needed).
|
/// This is followed by a FsAccessControl data block, as well as SrvAccessControl and KernelCapability descriptors, each one aligned to a 0x10 byte boundary using zero padding (if needed).
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u32 magic;
|
u32 magic;
|
||||||
u8 reserved_1[0xC];
|
u8 reserved_1[0xC];
|
||||||
|
@ -167,13 +167,13 @@ typedef enum {
|
||||||
NpdmFsAccessControlFlags_FullPermission = BIT_LONG(63)
|
NpdmFsAccessControlFlags_FullPermission = BIT_LONG(63)
|
||||||
} NpdmFsAccessControlFlags;
|
} NpdmFsAccessControlFlags;
|
||||||
|
|
||||||
/// AcidFsAccessControl descriptor. Part of the ACID section body.
|
/// FsAccessControl descriptor. Part of the ACID section body.
|
||||||
/// This is followed by:
|
/// This is followed by:
|
||||||
/// * 'content_owner_id_count' content owner IDs.
|
/// * 'content_owner_id_count' content owner IDs.
|
||||||
/// * 'save_data_owner_id_count' save data owner IDs.
|
/// * 'save_data_owner_id_count' save data owner IDs.
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u8 version; ///< Always non-zero. Usually set to 1.
|
u8 version; ///< Always non-zero. Usually set to 1.
|
||||||
u8 content_owner_id_count;
|
u8 content_owner_id_count;
|
||||||
u8 save_data_owner_id_count;
|
u8 save_data_owner_id_count;
|
||||||
u8 reserved;
|
u8 reserved;
|
||||||
|
@ -182,13 +182,13 @@ typedef struct {
|
||||||
u64 content_owner_id_max;
|
u64 content_owner_id_max;
|
||||||
u64 save_data_owner_id_min;
|
u64 save_data_owner_id_min;
|
||||||
u64 save_data_owner_id_max;
|
u64 save_data_owner_id_max;
|
||||||
} NpdmAcidFsAccessControlDescriptor;
|
} NpdmFsAccessControlDescriptor;
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
/// AciFsAccessControl descriptor. Part of the ACI0 section body.
|
/// FsAccessControl data. Part of the ACI0 section body.
|
||||||
/// This is followed by:
|
/// This is followed by:
|
||||||
/// * A NpdmAciFsAccessControlDescriptorContentOwnerBlock if 'content_owner_info_size' is greater than zero.
|
/// * A NpdmFsAccessControlDataContentOwnerBlock if 'content_owner_info_size' is greater than zero.
|
||||||
/// * A NpdmAciFsAccessControlDescriptorSaveDataOwnerBlock if 'save_data_owner_info_size' is greater than zero.
|
/// * A NpdmFsAccessControlDataSaveDataOwnerBlock if 'save_data_owner_info_size' is greater than zero.
|
||||||
/// * If available, this block is padded to a 0x4-byte boundary and followed by 'save_data_owner_id_count' save data owner IDs.
|
/// * If available, this block is padded to a 0x4-byte boundary and followed by 'save_data_owner_id_count' save data owner IDs.
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -199,26 +199,26 @@ typedef struct {
|
||||||
u32 content_owner_info_size;
|
u32 content_owner_info_size;
|
||||||
u32 save_data_owner_info_offset; ///< Relative to the start of this block. Only valid if 'save_data_owner_info_size' is greater than 0.
|
u32 save_data_owner_info_offset; ///< Relative to the start of this block. Only valid if 'save_data_owner_info_size' is greater than 0.
|
||||||
u32 save_data_owner_info_size;
|
u32 save_data_owner_info_size;
|
||||||
} NpdmAciFsAccessControlDescriptor;
|
} NpdmFsAccessControlData;
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
/// Placed after NpdmAciFsAccessControlDescriptor if its 'content_owner_info_size' member is greater than zero.
|
/// Placed after NpdmFsAccessControlData if its 'content_owner_info_size' member is greater than zero.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u32 content_owner_id_count;
|
u32 content_owner_id_count;
|
||||||
u64 content_owner_id[]; ///< 'content_owner_id_count' content owned IDs.
|
u64 content_owner_id[]; ///< 'content_owner_id_count' content owned IDs.
|
||||||
} NpdmAciFsAccessControlDescriptorContentOwnerBlock;
|
} NpdmFsAccessControlDataContentOwnerBlock;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
NpdmAccessibility_Read = BIT(0),
|
NpdmAccessibility_Read = BIT(0),
|
||||||
NpdmAccessibility_Write = BIT(1)
|
NpdmAccessibility_Write = BIT(1)
|
||||||
} NpdmAccessibility;
|
} NpdmAccessibility;
|
||||||
|
|
||||||
/// Placed after NpdmAciFsAccessControlDescriptor / NpdmAciFsAccessControlDescriptorContentOwnerBlock if the 'content_owner_info_size' member from NpdmAciFsAccessControlDescriptor is greater than zero.
|
/// Placed after NpdmFsAccessControlData / NpdmFsAccessControlDataContentOwnerBlock if the 'content_owner_info_size' member from NpdmFsAccessControlData is greater than zero.
|
||||||
/// If available, this block is padded to a 0x4-byte boundary and followed by 'save_data_owner_id_count' save data owner IDs.
|
/// If available, this block is padded to a 0x4-byte boundary and followed by 'save_data_owner_id_count' save data owner IDs.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u32 save_data_owner_id_count;
|
u32 save_data_owner_id_count;
|
||||||
u8 accessibility[]; ///< 'save_data_owner_id_count' NpdmAccessibility fields.
|
u8 accessibility[]; ///< 'save_data_owner_id_count' NpdmAccessibility fields.
|
||||||
} NpdmAciFsAccessControlDescriptorSaveDataOwnerBlock;
|
} NpdmFsAccessControlDataSaveDataOwnerBlock;
|
||||||
|
|
||||||
/// SrvAccessControl descriptor. Part of the ACID and ACI0 section bodies.
|
/// SrvAccessControl descriptor. Part of the ACID and ACI0 section bodies.
|
||||||
/// This descriptor is composed of a variable number of NpdmSrvAccessControlDescriptorEntry elements, each one with a variable size.
|
/// This descriptor is composed of a variable number of NpdmSrvAccessControlDescriptorEntry elements, each one with a variable size.
|
||||||
|
@ -543,11 +543,11 @@ typedef struct {
|
||||||
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.
|
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'.
|
NpdmMetaHeader *meta_header; ///< Pointer to the NpdmMetaHeader within 'raw_data'.
|
||||||
NpdmAcidHeader *acid_header; ///< Pointer to the NpdmAcidHeader 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.
|
NpdmFsAccessControlDescriptor *acid_fac_descriptor; ///< Pointer to the NpdmFsAccessControlDescriptor within the NPDM ACID section.
|
||||||
NpdmSrvAccessControlDescriptorEntry *acid_sac_descriptor; ///< Pointer to the first NpdmSrvAccessControlDescriptorEntry within the NPDM ACID section, if available.
|
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.
|
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'.
|
NpdmAciHeader *aci_header; ///< Pointer to the NpdmAciHeader within 'raw_data'.
|
||||||
NpdmAciFsAccessControlDescriptor *aci_fac_descriptor; ///< Pointer to the NpdmAciFsAccessControlDescriptor within the NPDM ACI0 section.
|
NpdmFsAccessControlData *aci_fac_data; ///< Pointer to the NpdmFsAccessControlData within the NPDM ACI0 section.
|
||||||
NpdmSrvAccessControlDescriptorEntry *aci_sac_descriptor; ///< Pointer to the first NpdmSrvAccessControlDescriptorEntry within the NPDM ACI0 section, if available.
|
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.
|
NpdmKernelCapabilityDescriptorEntry *aci_kc_descriptor; ///< Pointer to the first NpdmKernelCapabilityDescriptorEntry within the NPDM ACI0 section, if available.
|
||||||
} NpdmContext;
|
} NpdmContext;
|
||||||
|
@ -573,7 +573,7 @@ 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 && \
|
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->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->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 && npdm_ctx->aci_fac_data && \
|
||||||
((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->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)));
|
((npdm_ctx->aci_header->kernel_capability_size && npdm_ctx->aci_kc_descriptor) || (!npdm_ctx->aci_header->kernel_capability_size && !npdm_ctx->aci_kc_descriptor)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ bool programInfoInitializeContext(ProgramInfoContext *out, NcaContext *nca_ctx)
|
||||||
programInfoFreeContext(out);
|
programInfoFreeContext(out);
|
||||||
|
|
||||||
/* Initialize Partition FS context. */
|
/* Initialize Partition FS context. */
|
||||||
if (!pfsInitializeContext(&(out->pfs_ctx), &(nca_ctx->fs_contexts[0])))
|
if (!pfsInitializeContext(&(out->pfs_ctx), &(nca_ctx->fs_ctx[0])))
|
||||||
{
|
{
|
||||||
LOGFILE("Failed to initialize Partition FS context!");
|
LOGFILE("Failed to initialize Partition FS context!");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -552,32 +552,32 @@ static bool programInfoIsElfSymbolValid(u8 *dynsym_ptr, char *dynstr_base_ptr, u
|
||||||
|
|
||||||
static bool programInfoAddFsAccessControlDataToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, ProgramInfoContext *program_info_ctx)
|
static bool programInfoAddFsAccessControlDataToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, ProgramInfoContext *program_info_ctx)
|
||||||
{
|
{
|
||||||
NpdmAciFsAccessControlDescriptor *aci_fac_descriptor = NULL;
|
NpdmFsAccessControlData *aci_fac_data = NULL;
|
||||||
NpdmAciFsAccessControlDescriptorSaveDataOwnerBlock *save_data_owner_block = NULL;
|
NpdmFsAccessControlDataSaveDataOwnerBlock *save_data_owner_block = NULL;
|
||||||
u64 *save_data_owner_ids = NULL;
|
u64 *save_data_owner_ids = NULL;
|
||||||
bool success = false, fac_data_available = false;
|
bool success = false, sdo_data_available = false;
|
||||||
|
|
||||||
if (!xml_buf || !xml_buf_size || !program_info_ctx || !(aci_fac_descriptor = program_info_ctx->npdm_ctx.aci_fac_descriptor))
|
if (!xml_buf || !xml_buf_size || !program_info_ctx || !(aci_fac_data = program_info_ctx->npdm_ctx.aci_fac_data))
|
||||||
{
|
{
|
||||||
LOGFILE("Invalid parameters!");
|
LOGFILE("Invalid parameters!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if there's save data owner data available in the FS access control data descriptor from the ACI0 section in the NPDM. */
|
/* Check if there's save data owner data available in the FS access control data region from the ACI0 section in the NPDM. */
|
||||||
fac_data_available = (aci_fac_descriptor->save_data_owner_info_offset >= sizeof(NpdmAciFsAccessControlDescriptor) && aci_fac_descriptor->save_data_owner_info_size);
|
sdo_data_available = (aci_fac_data->save_data_owner_info_offset >= sizeof(NpdmFsAccessControlData) && aci_fac_data->save_data_owner_info_size);
|
||||||
if (!fac_data_available) goto end;
|
if (!sdo_data_available) goto end;
|
||||||
|
|
||||||
/* Get save data owner block and check the ID count. */
|
/* Get save data owner block and check the ID count. */
|
||||||
save_data_owner_block = (NpdmAciFsAccessControlDescriptorSaveDataOwnerBlock*)((u8*)aci_fac_descriptor + aci_fac_descriptor->save_data_owner_info_offset);
|
save_data_owner_block = (NpdmFsAccessControlDataSaveDataOwnerBlock*)((u8*)aci_fac_data + aci_fac_data->save_data_owner_info_offset);
|
||||||
if (!save_data_owner_block->save_data_owner_id_count)
|
if (!save_data_owner_block->save_data_owner_id_count)
|
||||||
{
|
{
|
||||||
fac_data_available = false;
|
sdo_data_available = false;
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get save data owner IDs. */
|
/* Get save data owner IDs. */
|
||||||
/* Padding to a 0x4-byte boundary is needed. Each accessibility field takes up a single byte, so we can get away with it by aligning the ID count. */
|
/* Padding to a 0x4-byte boundary is needed. Each accessibility field takes up a single byte, so we can get away with it by aligning the ID count. */
|
||||||
save_data_owner_ids = (u64*)((u8*)save_data_owner_block + sizeof(NpdmAciFsAccessControlDescriptorSaveDataOwnerBlock) + ALIGN_UP(save_data_owner_block->save_data_owner_id_count, 0x4));
|
save_data_owner_ids = (u64*)((u8*)save_data_owner_block + sizeof(NpdmFsAccessControlDataSaveDataOwnerBlock) + ALIGN_UP(save_data_owner_block->save_data_owner_id_count, 0x4));
|
||||||
|
|
||||||
if (!utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, " <FsAccessControlData>\n")) goto end;
|
if (!utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, " <FsAccessControlData>\n")) goto end;
|
||||||
|
|
||||||
|
@ -597,7 +597,7 @@ static bool programInfoAddFsAccessControlDataToAuthoringToolXml(char **xml_buf,
|
||||||
|
|
||||||
end:
|
end:
|
||||||
/* Append an empty XML element if no FS access control data exists. */
|
/* Append an empty XML element if no FS access control data exists. */
|
||||||
if (!success && !fac_data_available) success = utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, " <FsAccessControlData />\n");
|
if (!success && !sdo_data_available) success = utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, " <FsAccessControlData />\n");
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
1
todo.txt
1
todo.txt
|
@ -1,7 +1,6 @@
|
||||||
todo:
|
todo:
|
||||||
|
|
||||||
nca: functions for fs section lookup? (could just let the user choose...)
|
nca: functions for fs section lookup? (could just let the user choose...)
|
||||||
nca: add encrypted headers to nca context, avoid re-encrypting plaintext headers in place
|
|
||||||
nca: function to write re-encrypted nca headers / nca fs headers (don't forget nca0 please)
|
nca: function to write re-encrypted nca headers / nca fs headers (don't forget nca0 please)
|
||||||
|
|
||||||
tik: option to wipe elicense property mask
|
tik: option to wipe elicense property mask
|
||||||
|
|
Loading…
Reference in a new issue