1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-22 18:26:39 +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:
Pablo Curiel 2020-10-13 10:00:03 -04:00
parent 42fef7d3f1
commit ba4fdcd01c
12 changed files with 149 additions and 134 deletions

View file

@ -313,7 +313,7 @@ int main(int argc, char *argv[])
} else
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();
utilsChangeHomeButtonBlockStatus(true);
dumpFsSection(cur_title_info, &(nca_ctx->fs_contexts[selected_idx]));
dumpFsSection(cur_title_info, &(nca_ctx->fs_ctx[selected_idx]));
utilsChangeHomeButtonBlockStatus(false);
}

View file

@ -501,7 +501,7 @@ int main(int argc, char *argv[])
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");
goto out2;
@ -512,7 +512,7 @@ int main(int argc, char *argv[])
consolePrint("bktr initialize ctx succeeded\n");
} 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");
goto out2;

View file

@ -53,7 +53,7 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
cnmtFreeContext(out);
/* 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!");
goto end;

View file

@ -107,7 +107,7 @@ typedef enum {
/// 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.
/// 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.
/// * (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 {

View file

@ -41,7 +41,7 @@ bool legalInfoInitializeContext(LegalInfoContext *out, NcaContext *nca_ctx)
legalInfoFreeContext(out);
/* 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!");
goto end;

View file

@ -216,7 +216,7 @@ bool nacpInitializeContext(NacpContext *out, NcaContext *nca_ctx)
nacpFreeContext(out);
/* 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!");
goto end;

View file

@ -42,7 +42,7 @@ static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = {
NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info);
static bool ncaDecryptHeader(NcaContext *ctx);
static bool ncaReadDecryptedHeader(NcaContext *ctx);
static bool ncaDecryptKeyArea(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. */
if (!ncaReadContentFile(out, &(out->header), sizeof(NcaHeader), 0))
/* Read decrypted NCA header and NCA FS section headers. */
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;
}
/* 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)
{
/* Retrieve ticket. */
@ -163,34 +153,37 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
/* Parse sections. */
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. */
out->fs_contexts[i].nca_ctx = out;
out->fs_contexts[i].section_num = i;
out->fs_contexts[i].section_type = NcaFsSectionType_Invalid; /* Placeholder. */
fs_ctx->nca_ctx = out;
fs_ctx->section_num = i;
fs_ctx->section_type = NcaFsSectionType_Invalid; /* Placeholder. */
/* 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. */
out->fs_contexts[i].section_offset = NCA_FS_SECTOR_OFFSET(out->header.fs_info[i].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_offset = NCA_FS_SECTOR_OFFSET(fs_info->start_sector);
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. */
if (out->fs_contexts[i].section_offset < sizeof(NcaHeader) || !out->fs_contexts[i].section_size || \
(out->fs_contexts[i].section_offset + out->fs_contexts[i].section_size) > out->content_size) continue;
if (fs_ctx->section_offset < sizeof(NcaHeader) || !fs_ctx->section_size || \
(fs_ctx->section_offset + fs_ctx->section_size) > out->content_size) continue;
/* Determine encryption type. */
out->fs_contexts[i].encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_AesXts : out->fs_contexts[i].header.encryption_type);
if (out->fs_contexts[i].encryption_type == NcaEncryptionType_Auto)
fs_ctx->encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_AesXts : fs_ctx->header.encryption_type);
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 1: /* RomFS. */
out->fs_contexts[i].encryption_type = NcaEncryptionType_AesCtr;
fs_ctx->encryption_type = NcaEncryptionType_AesCtr;
break;
case 2: /* Logo Partition FS. */
out->fs_contexts[i].encryption_type = NcaEncryptionType_None;
fs_ctx->encryption_type = NcaEncryptionType_None;
break;
default:
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. */
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. */
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
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
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. */
if (out->fs_contexts[i].section_type >= NcaFsSectionType_Invalid) continue;
if (fs_ctx->section_type >= NcaFsSectionType_Invalid) continue;
/* Initialize crypto data. */
if ((!out->rights_id_available || (out->rights_id_available && out->titlekey_retrieved)) && out->fs_contexts[i].encryption_type > NcaEncryptionType_None && \
out->fs_contexts[i].encryption_type <= NcaEncryptionType_AesCtrEx)
if ((!out->rights_id_available || (out->rights_id_available && out->titlekey_retrieved)) && fs_ctx->encryption_type > NcaEncryptionType_None && \
fs_ctx->encryption_type <= NcaEncryptionType_AesCtrEx)
{
/* Initialize section CTR. */
ncaInitializeAesCtrIv(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. */
if (out->rights_id_available)
{
/* 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 {
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. */
aes128XtsContextCreate(&(out->fs_contexts[i].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_decrypt_ctx), out->decrypted_key_area.aes_xts_1, out->decrypted_key_area.aes_xts_2, false);
aes128XtsContextCreate(&(fs_ctx->xts_encrypt_ctx), out->decrypted_key_area.aes_xts_1, out->decrypted_key_area.aes_xts_2, true);
} 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. */
out->fs_contexts[i].enabled = true;
fs_ctx->enabled = true;
}
return true;
@ -356,8 +349,10 @@ void ncaRemoveTitlekeyCrypto(NcaContext *ctx)
for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++)
{
/* 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;
u8 *key_ptr = (ctx->fs_contexts[i].encryption_type == NcaEncryptionType_AesCtr ? ctx->decrypted_key_area.aes_ctr : ctx->decrypted_key_area.aes_ctr_ex);
NcaFsSectionContext *fs_ctx = &(ctx->fs_ctx[i]);
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);
}
@ -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);
/* 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))
{
LOGFILE("Error encrypting NCA \"%s\" header!", ctx->content_id_str);
@ -404,20 +399,22 @@ bool ncaEncryptHeader(NcaContext *ctx)
/* 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. */
/* 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++)
{
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. */
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. */
/* 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. */
/* 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);
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))
{
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);
}
static bool ncaDecryptHeader(NcaContext *ctx)
static bool ncaReadDecryptedHeader(NcaContext *ctx)
{
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();
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. */
aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + AES_128_KEY_SIZE, false);
/* 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);
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->key_generation = ncaGetKeyGenerationValue(ctx);
ctx->rights_id_available = ncaCheckRightsIdAvailability(ctx);
sha256CalculateHash(ctx->header_hash, &(ctx->header), sizeof(NcaHeader));
/* Decrypt NCA key area (if needed). */
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. */
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. */
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. */
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));
if (!ncaReadContentFile(ctx, &(ctx->fs_contexts[i].header), sizeof(NcaFsHeader), fs_header_offset))
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, &(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);
return false;
@ -507,9 +515,9 @@ static bool ncaDecryptHeader(NcaContext *ctx)
/* 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. */
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))
{
LOGFILE("Error decrypting NCA%u \"%s\" FS section header #%u!", ctx->format_version, ctx->content_id_str, i);

View file

@ -270,10 +270,14 @@ typedef enum {
NcaFsSectionType_Invalid = 4
} 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 {
bool enabled;
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;
u64 section_offset;
u64 section_size;
@ -299,26 +303,28 @@ typedef struct {
} NcaDecryptedKeyArea;
typedef struct {
u8 storage_id; ///< NcmStorageId.
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.
NcmContentId content_id; ///< Also used to read NCA data.
u8 storage_id; ///< NcmStorageId.
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.
NcmContentId content_id; ///< Also used to read NCA data.
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];
u8 format_version; ///< NcaVersion.
u8 content_type; ///< NcmContentType. Retrieved from NcmContentInfo.
u64 content_size; ///< Retrieved from NcmContentInfo.
u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header.
u8 id_offset; ///< Retrieved from NcmContentInfo.
u8 format_version; ///< NcaVersion.
u8 content_type; ///< NcmContentType. Retrieved from NcmContentInfo.
u64 content_size; ///< Retrieved from NcmContentInfo.
u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header.
u8 id_offset; ///< Retrieved from NcmContentInfo.
bool rights_id_available;
bool titlekey_retrieved;
u8 titlekey[AES_128_KEY_SIZE]; ///< Decrypted titlekey from the ticket.
NcaHeader header; ///< 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.
NcaFsSectionContext fs_contexts[NCA_FS_HEADER_COUNT];
u8 titlekey[AES_128_KEY_SIZE]; ///< Decrypted titlekey from the ticket.
NcaHeader header; ///< Plaintext NCA header.
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.
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;
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;
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.
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.
/// 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.
/// 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);
/// 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.
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);
/// Updates the content ID and hash from a NCA context using a provided SHA-256 checksum.

View file

@ -156,14 +156,14 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
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)
{
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);
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)
{
@ -212,14 +212,14 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
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)
{
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);
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)
{

View file

@ -51,23 +51,23 @@ typedef struct {
/// This is the start of every NPDM file.
/// This is followed by ACID and ACI0 sections, both with variable offsets and sizes.
typedef struct {
u32 magic; ///< "NPDM".
u32 magic; ///< "NPDM".
u8 acid_signature_key_generation;
u8 reserved_1[0x7];
NpdmMetaFlags flags;
u8 reserved_2;
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_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 NPDM_SYSTEM_RESOURCE_MAX_SIZE.
u32 system_resource_size; ///< Must not exceed NPDM_SYSTEM_RESOURCE_MAX_SIZE.
VersionType1 version;
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.
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];
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 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;
} NpdmMetaHeader;
@ -90,27 +90,27 @@ typedef struct {
} NpdmAcidFlags;
/// 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 {
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.
u32 magic; ///< "ACID".
u32 size; ///< Must be equal to ACID section size from the META header minus 0x100 (ACID signature size).
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.
u32 magic; ///< "ACID".
u32 size; ///< Must be equal to ACID section size from the META header minus 0x100 (ACID signature size).
u8 reserved_1[0x4];
NpdmAcidFlags flags;
u64 program_id_min;
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 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 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;
u8 reserved_2[0x8];
} NpdmAcidHeader;
/// 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 {
u32 magic;
u8 reserved_1[0xC];
@ -167,13 +167,13 @@ typedef enum {
NpdmFsAccessControlFlags_FullPermission = BIT_LONG(63)
} NpdmFsAccessControlFlags;
/// AcidFsAccessControl descriptor. Part of the ACID section body.
/// FsAccessControl descriptor. Part of the ACID section body.
/// This is followed by:
/// * 'content_owner_id_count' content owner IDs.
/// * 'save_data_owner_id_count' save data owner IDs.
#pragma pack(push, 1)
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 save_data_owner_id_count;
u8 reserved;
@ -182,13 +182,13 @@ typedef struct {
u64 content_owner_id_max;
u64 save_data_owner_id_min;
u64 save_data_owner_id_max;
} NpdmAcidFsAccessControlDescriptor;
} NpdmFsAccessControlDescriptor;
#pragma pack(pop)
/// AciFsAccessControl descriptor. Part of the ACI0 section body.
/// FsAccessControl data. Part of the ACI0 section body.
/// This is followed by:
/// * A NpdmAciFsAccessControlDescriptorContentOwnerBlock if 'content_owner_info_size' is greater than zero.
/// * A NpdmAciFsAccessControlDescriptorSaveDataOwnerBlock if 'save_data_owner_info_size' is greater than zero.
/// * A NpdmFsAccessControlDataContentOwnerBlock if 'content_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.
#pragma pack(push, 1)
typedef struct {
@ -199,26 +199,26 @@ typedef struct {
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_size;
} NpdmAciFsAccessControlDescriptor;
} NpdmFsAccessControlData;
#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 {
u32 content_owner_id_count;
u64 content_owner_id[]; ///< 'content_owner_id_count' content owned IDs.
} NpdmAciFsAccessControlDescriptorContentOwnerBlock;
} NpdmFsAccessControlDataContentOwnerBlock;
typedef enum {
NpdmAccessibility_Read = BIT(0),
NpdmAccessibility_Write = BIT(1)
} 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.
typedef struct {
u32 save_data_owner_id_count;
u8 accessibility[]; ///< 'save_data_owner_id_count' NpdmAccessibility fields.
} NpdmAciFsAccessControlDescriptorSaveDataOwnerBlock;
} NpdmFsAccessControlDataSaveDataOwnerBlock;
/// 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.
@ -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.
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.
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.
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.
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.
NpdmKernelCapabilityDescriptorEntry *aci_kc_descriptor; ///< Pointer to the first NpdmKernelCapabilityDescriptorEntry within the NPDM ACI0 section, if available.
} 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 && \
((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 && 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->kernel_capability_size && npdm_ctx->aci_kc_descriptor) || (!npdm_ctx->aci_header->kernel_capability_size && !npdm_ctx->aci_kc_descriptor)));
}

View file

@ -71,7 +71,7 @@ bool programInfoInitializeContext(ProgramInfoContext *out, NcaContext *nca_ctx)
programInfoFreeContext(out);
/* 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!");
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)
{
NpdmAciFsAccessControlDescriptor *aci_fac_descriptor = NULL;
NpdmAciFsAccessControlDescriptorSaveDataOwnerBlock *save_data_owner_block = NULL;
NpdmFsAccessControlData *aci_fac_data = NULL;
NpdmFsAccessControlDataSaveDataOwnerBlock *save_data_owner_block = 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!");
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. */
fac_data_available = (aci_fac_descriptor->save_data_owner_info_offset >= sizeof(NpdmAciFsAccessControlDescriptor) && aci_fac_descriptor->save_data_owner_info_size);
if (!fac_data_available) goto end;
/* Check if there's save data owner data available in the FS access control data region from the ACI0 section in the NPDM. */
sdo_data_available = (aci_fac_data->save_data_owner_info_offset >= sizeof(NpdmFsAccessControlData) && aci_fac_data->save_data_owner_info_size);
if (!sdo_data_available) goto end;
/* 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)
{
fac_data_available = false;
sdo_data_available = false;
goto end;
}
/* 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. */
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;
@ -597,7 +597,7 @@ static bool programInfoAddFsAccessControlDataToAuthoringToolXml(char **xml_buf,
end:
/* 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;
}

View file

@ -1,7 +1,6 @@
todo:
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)
tik: option to wipe elicense property mask