diff --git a/code_templates/usb_romfs_dumper.c b/code_templates/usb_romfs_dumper.c
index 25d69c4..df6f9e1 100644
--- a/code_templates/usb_romfs_dumper.c
+++ b/code_templates/usb_romfs_dumper.c
@@ -378,7 +378,7 @@ int main(int argc, char *argv[])
while(true)
{
consoleClear();
- printf("select an user application to dump its romfs.\nif an update is available, patch romfs data will be dumped instead.\ndata will be transferred via usb.\npress b to exit.\n\n");
+ printf("select a user application to dump its romfs.\nif an update is available, patch romfs data will be dumped instead.\ndata will be transferred via usb.\npress b to exit.\n\n");
printf("title: %u / %u\n", selected_idx + 1, app_count);
printf("selected title: %016lX - %s\n\n", app_metadata[selected_idx]->title_id, app_metadata[selected_idx]->lang_entry.name);
diff --git a/code_templates/xml_generator.c b/code_templates/xml_generator.c
index 1e015dc..b835f00 100644
--- a/code_templates/xml_generator.c
+++ b/code_templates/xml_generator.c
@@ -104,7 +104,7 @@ int main(int argc, char *argv[])
while(true)
{
consoleClear();
- printf("select an user application to generate xmls for.\npress b to exit.\n\n");
+ printf("select a user application to generate xmls for.\npress b to exit.\n\n");
printf("title: %u / %u\n", selected_idx + 1, app_count);
printf("selected title: %016lX - %s\n\n", app_metadata[selected_idx]->title_id, app_metadata[selected_idx]->lang_entry.name);
diff --git a/source/cnmt.c b/source/cnmt.c
index eca33f8..59b353a 100644
--- a/source/cnmt.c
+++ b/source/cnmt.c
@@ -24,6 +24,14 @@
#define CNMT_MINIMUM_FILENAME_LENGTH 23 /* Content Meta Type + "_" + Title ID + ".cnmt". */
+/* Global variables. */
+
+static const char *g_cnmtAttributeStrings[ContentMetaAttribute_Count] = {
+ "IncludesExFatDriver",
+ "Rebootless",
+ "Compacted"
+};
+
/* Function prototypes. */
static bool cnmtGetContentMetaTypeAndTitleIdFromFileName(const char *cnmt_filename, size_t cnmt_filename_len, u8 *out_content_meta_type, u64 *out_title_id);
@@ -247,6 +255,7 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_
char *xml_buf = NULL;
u64 xml_buf_size = 0;
char digest_str[0x41] = {0};
+ u8 count = 0;
bool success = false, invalid_nca = false;
/* Free AuthoringTool-like XML data if needed. */
@@ -260,10 +269,23 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_
" %s\n" \
" 0x%016lx\n" \
" %u\n" \
- " %u\n", \
+ " \n" \
+ " \n",
titleGetNcmContentMetaTypeName(cnmt_ctx->packaged_header->content_meta_type), \
cnmt_ctx->packaged_header->title_id, \
- cnmt_ctx->packaged_header->version.value,
+ cnmt_ctx->packaged_header->version.value)) goto end;
+
+ /* ContentMetaAttribute. */
+ for(i = 0; i < ContentMetaAttribute_Count; i++)
+ {
+ if (!(cnmt_ctx->packaged_header->content_meta_attribute & (u8)BIT(i))) continue;
+ if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " %s\n", g_cnmtAttributeStrings[i])) goto end;
+ count++;
+ }
+
+ if (!count && !utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " \n")) goto end;
+
+ if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " %u\n", \
cnmt_ctx->packaged_header->required_download_system_version.value)) goto end;
for(i = 0; i < nca_ctx_count; i++)
@@ -309,8 +331,11 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_
utilsGenerateHexStringFromData(digest_str, sizeof(digest_str), cnmt_ctx->digest, CNMT_DIGEST_SIZE);
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
+ " \n" \
" %s\n" \
- " %u\n", \
+ " %u\n" \
+ " \n" \
+ " \n", \
digest_str, \
cnmt_ctx->nca_ctx->key_generation)) goto end;
@@ -326,12 +351,8 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
" <%s>%u%s>\n" \
" <%s>0x%016lx%s>\n", \
- required_title_version_str, \
- required_title_version, \
- required_title_version_str, \
- required_title_type_str, \
- required_title_id, \
- required_title_type_str)) goto end;
+ required_title_version_str, required_title_version, required_title_version_str, \
+ required_title_type_str, required_title_id, required_title_type_str)) goto end;
}
if (cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Application)
diff --git a/source/cnmt.h b/source/cnmt.h
index 7203d9c..524ca3a 100644
--- a/source/cnmt.h
+++ b/source/cnmt.h
@@ -31,7 +31,8 @@
typedef enum {
ContentMetaAttribute_IncludesExFatDriver = BIT(0),
ContentMetaAttribute_Rebootless = BIT(1),
- ContentMetaAttribute_Compacted = BIT(2)
+ ContentMetaAttribute_Compacted = BIT(2),
+ ContentMetaAttribute_Count = 3 ///< Total values supported by this enum.
} ContentMetaAttribute;
typedef enum {
diff --git a/source/nca.c b/source/nca.c
index 15089db..d64a473 100644
--- a/source/nca.c
+++ b/source/nca.c
@@ -59,7 +59,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val, bool lock);
static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, void *out, bool is_integrity_patch);
-static void ncaWriteHashDataPatchToMemoryBuffer(NcaContext *ctx, NcaHashDataPatch *layer_patch, void *buf, u64 buf_size, u64 buf_offset);
+static void ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 patch_offset, u64 patch_size, void *buf, u64 buf_offset, u64 buf_size);
static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock);
@@ -297,7 +297,11 @@ void ncaWriteHierarchicalSha256PatchToMemoryBuffer(NcaContext *ctx, NcaHierarchi
if (!ctx || !strlen(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH || !patch || memcmp(patch->content_id.c, ctx->content_id.c, 0x10) != 0 || !patch->hash_region_count || \
patch->hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT || !buf || !buf_size || buf_offset >= ctx->content_size || (buf_offset + buf_size) > ctx->content_size) return;
- for(u32 i = 0; i < patch->hash_region_count; i++) ncaWriteHashDataPatchToMemoryBuffer(ctx, &(patch->hash_region_patch[i]), buf, buf_size, buf_offset);
+ for(u32 i = 0; i < patch->hash_region_count; i++)
+ {
+ NcaHashDataPatch *hash_region_patch = &(patch->hash_region_patch[i]);
+ ncaWritePatchToMemoryBuffer(ctx, hash_region_patch->data, hash_region_patch->offset, hash_region_patch->size, buf, buf_offset, buf_size);
+ }
}
bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalIntegrityPatch *out)
@@ -310,34 +314,11 @@ void ncaWriteHierarchicalIntegrityPatchToMemoryBuffer(NcaContext *ctx, NcaHierar
if (!ctx || !strlen(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH || !patch || memcmp(patch->content_id.c, ctx->content_id.c, 0x10) != 0 || !buf || !buf_size || \
buf_offset >= ctx->content_size || (buf_offset + buf_size) > ctx->content_size) return;
- for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++) ncaWriteHashDataPatchToMemoryBuffer(ctx, &(patch->hash_level_patch[i]), buf, buf_size, buf_offset);
-}
-
-const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx)
-{
- NcaContext *nca_ctx = NULL;
- const char *str = "Invalid";
- if (!ctx || !ctx->enabled || !(nca_ctx = (NcaContext*)ctx->nca_ctx)) return str;
-
- switch(ctx->section_type)
+ for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++)
{
- case NcaFsSectionType_PartitionFs:
- str = ((nca_ctx->content_type == NcmContentType_Program && ctx->section_num == 0) ? "ExeFS" : "Partition FS");
- break;
- case NcaFsSectionType_RomFs:
- str = "RomFS";
- break;
- case NcaFsSectionType_PatchRomFs:
- str = "Patch RomFS [BKTR]";
- break;
- case NcaFsSectionType_Nca0RomFs:
- str = "NCA0 RomFS";
- break;
- default:
- break;
+ NcaHashDataPatch *hash_level_patch = &(patch->hash_level_patch[i]);
+ ncaWritePatchToMemoryBuffer(ctx, hash_level_patch->data, hash_level_patch->offset, hash_level_patch->size, buf, buf_offset, buf_size);
}
-
- return str;
}
void ncaRemoveTitlekeyCrypto(NcaContext *ctx)
@@ -425,6 +406,27 @@ bool ncaEncryptHeader(NcaContext *ctx)
return true;
}
+void ncaWriteEncryptedHeaderDataToMemoryBuffer(NcaContext *ctx, void *buf, u64 buf_size, u64 buf_offset)
+{
+ /* Return right away if we're dealing with invalid parameters, or if the buffer data is not part of the range covered by the encrypted header data (NCA2/3 optimization, last condition). */
+ /* In order to avoid taking up too much execution time when this function is called (ideally inside a loop), we won't use ncaIsHeaderDirty() here. Let the user take care of it instead. */
+ if (!ctx || !strlen(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH || !buf || !buf_size || buf_offset >= ctx->content_size || \
+ (buf_offset + buf_size) > ctx->content_size || (ctx->format_version != NcaVersion_Nca0 && buf_offset >= NCA_FULL_HEADER_LENGTH)) return;
+
+ /* Attempt to write the NCA header. */
+ if (buf_offset < sizeof(NcaHeader)) ncaWritePatchToMemoryBuffer(ctx, &(ctx->encrypted_header), 0, sizeof(NcaHeader), buf, buf_offset, buf_size);
+
+ /* Attempt to write NCA FS section headers. */
+ for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++)
+ {
+ NcaFsSectionContext *fs_ctx = &(ctx->fs_ctx[i]);
+ if (!fs_ctx->enabled) continue;
+
+ u64 fs_header_offset = (ctx->format_version != NcaVersion_Nca0 ? (sizeof(NcaHeader) + (i * sizeof(NcaFsHeader))) : fs_ctx->section_offset);
+ ncaWritePatchToMemoryBuffer(ctx, &(fs_ctx->encrypted_header), fs_header_offset, sizeof(NcaFsHeader), buf, buf_offset, buf_size);
+ }
+}
+
void ncaUpdateContentIdAndHash(NcaContext *ctx, u8 hash[SHA256_HASH_SIZE])
{
if (!ctx) return;
@@ -436,6 +438,33 @@ void ncaUpdateContentIdAndHash(NcaContext *ctx, u8 hash[SHA256_HASH_SIZE])
utilsGenerateHexStringFromData(ctx->hash_str, sizeof(ctx->hash_str), ctx->hash, sizeof(ctx->hash));
}
+const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx)
+{
+ NcaContext *nca_ctx = NULL;
+ const char *str = "Invalid";
+ if (!ctx || !ctx->enabled || !(nca_ctx = (NcaContext*)ctx->nca_ctx)) return str;
+
+ switch(ctx->section_type)
+ {
+ case NcaFsSectionType_PartitionFs:
+ str = ((nca_ctx->content_type == NcmContentType_Program && ctx->section_num == 0) ? "ExeFS" : "Partition FS");
+ break;
+ case NcaFsSectionType_RomFs:
+ str = "RomFS";
+ break;
+ case NcaFsSectionType_PatchRomFs:
+ str = "Patch RomFS [BKTR]";
+ break;
+ case NcaFsSectionType_Nca0RomFs:
+ str = "NCA0 RomFS";
+ break;
+ default:
+ break;
+ }
+
+ return str;
+}
+
NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info)
{
if (!fs_info) return false;
@@ -1106,20 +1135,22 @@ end:
return success;
}
-static void ncaWriteHashDataPatchToMemoryBuffer(NcaContext *ctx, NcaHashDataPatch *layer_patch, void *buf, u64 buf_size, u64 buf_offset)
+static void ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 patch_offset, u64 patch_size, void *buf, u64 buf_offset, u64 buf_size)
{
/* Return right away if we're dealing with invalid parameters, or if the buffer data is not part of the range covered by the patch (last two conditions). */
- if (!ctx || !layer_patch || layer_patch->offset < sizeof(NcaHeader) || layer_patch->offset >= ctx->content_size || !layer_patch->size || !layer_patch->data || \
- (layer_patch->offset + layer_patch->size) > ctx->content_size || !buf || (buf_offset + buf_size) <= layer_patch->offset || (layer_patch->offset + layer_patch->size) <= buf_offset) return;
+ if (!ctx || !patch || patch_offset >= ctx->content_size || !patch_size || (patch_offset + patch_size) > ctx->content_size || (buf_offset + buf_size) <= patch_offset || \
+ (patch_offset + patch_size) <= buf_offset) return;
/* Overwrite buffer data using patch data. */
- u64 patch_block_offset = (buf_offset > layer_patch->offset ? (buf_offset - layer_patch->offset) : 0);
- u64 patch_block_size = (layer_patch->size - patch_block_offset);
+ u64 patch_block_offset = (patch_offset < buf_offset ? (buf_offset - patch_offset) : 0);
+ u64 patch_remaining_size = (patch_size - patch_block_offset);
- u64 buf_block_offset = (buf_offset > layer_patch->offset ? 0 : (layer_patch->offset - buf_offset));
- u64 buf_block_size = ((buf_size - buf_block_offset) > patch_block_size ? patch_block_size : (buf_size - buf_block_offset));
+ u64 buf_block_offset = (buf_offset < patch_offset ? (patch_offset - buf_offset) : 0);
+ u64 buf_remaining_size = (buf_size - buf_block_offset);
- memcpy((u8*)buf + buf_block_offset, layer_patch->data + patch_block_offset, buf_block_size);
+ u64 buf_block_size = (buf_remaining_size < patch_remaining_size ? buf_remaining_size : patch_remaining_size);
+
+ memcpy((u8*)buf + buf_block_offset, (const u8*)patch + patch_block_offset, buf_block_size);
LOGFILE("Overwrote 0x%lX bytes block at offset 0x%lX from raw NCA \"%s\" buffer (size 0x%lX, NCA offset 0x%lX).", buf_block_size, buf_block_offset, ctx->content_id_str, buf_size, buf_offset);
}
diff --git a/source/nca.h b/source/nca.h
index 8b66a6b..de33424 100644
--- a/source/nca.h
+++ b/source/nca.h
@@ -396,39 +396,25 @@ bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void
/// 'buf_offset' must hold the raw NCA offset where the data stored in 'buf' was read from.
void ncaWriteHierarchicalIntegrityPatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalIntegrityPatch *patch, void *buf, u64 buf_size, u64 buf_offset);
-/// Returns a pointer to a string holding the name of the section type from the provided NCA FS section context.
-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.
/// The 'encrypted_header' member from the NCA context and its underlying NCA FS section contexts is updated by this function.
+/// Internally uses ncaIsHeaderDirty() to determine if NCA header / NCA FS section header re-encryption is needed.
bool ncaEncryptHeader(NcaContext *ctx);
+/// Used to replace the NCA header and the NCA FS section headers while writing a NCA if they were modified in any way.
+/// Overwrites block(s) from a buffer holding raw NCA data using a previously initialized NcaContext.
+/// 'buf_offset' must hold the raw NCA offset where the data stored in 'buf' was read from.
+/// Bear in mind this function doesn't call ncaIsHeaderDirty() on its own to avoid taking up too much execution time, so it will attempt to overwrite data even if it isn't needed.
+void ncaWriteEncryptedHeaderDataToMemoryBuffer(NcaContext *ctx, void *buf, u64 buf_size, u64 buf_offset);
+
/// Updates the content ID and hash from a NCA context using a provided SHA-256 checksum.
void ncaUpdateContentIdAndHash(NcaContext *ctx, u8 hash[SHA256_HASH_SIZE]);
-
-
-
-
-
-
-
-
-
-
-
-
+/// Returns a pointer to a string holding the name of the section type from the provided NCA FS section context.
+const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx);
/// Helper inline functions.
diff --git a/todo.txt b/todo.txt
index 8288a0c..88beb92 100644
--- a/todo.txt
+++ b/todo.txt
@@ -1,9 +1,66 @@
+reminder:
+
+list of top level functions designed to alter nca data in order of (possible) usage:
+
+ out of loop:
+ * ncaSetDownloadDistributionType (instead of always using it like legacy, offer it as an option)
+
+ * ncaRemoveTitlekeyCrypto (can be used with digital titles + game updates in gamecards)
+
+ * cnmtGenerateNcaPatch (Meta)
+ * calls pfsGenerateEntryPatch
+ * calls ncaGenerateHierarchicalSha256Patch
+ * cnmtIsNcaPatchRequired -> not sure if i'll keep this
+ * missing wrapper for pfsWriteEntryPatchToMemoryBuffer
+
+ * programInfoChangeAcidPublicKeyAndNcaSignature (Program)
+ * calls npdmChangeAcidPublicKeyAndNcaSignature (Program)
+ * requires programInfoGenerateNcaPatch to be effective
+ * calls npdmGenerateNcaPatch (Program)
+ * calls pfsGenerateEntryPatch
+ * calls ncaGenerateHierarchicalSha256Patch
+ * programInfoIsNcaPatchRequired -> not sure if i'll keep this
+ * calls npdmIsNcaPatchRequired -> not sure if i'll keep this
+ * call inside programInfoChangeAcidPublicKeyAndNcaSignature maybe ???
+ * missing wrapper for pfsWriteEntryPatchToMemoryBuffer
+
+ * nacpGenerateNcaPatch (Control)
+ * calls romfsGenerateFileEntryPatch
+ * calls ncaGenerateHierarchicalSha256Patch / ncaGenerateHierarchicalIntegrityPatch
+ * nacpIsNcaPatchRequired -> not sure if i'll keep this
+ * missing wrapper for romfsWriteFileEntryPatchToMemoryBuffer
+
+ * ncaIsHeaderDirty (doesn't modify anything per se, but it's used to check if any of the functions above has been used, basically)
+ * ncaEncryptHeader (doesn't modify anything per se, but it's used to generate new encrypted header data if needed)
+
+ inside loop:
+ * ncaWriteEncryptedHeaderDataToMemoryBuffer
+
+ * pfsWriteEntryPatchToMemoryBuffer
+ * calls ncaWriteHierarchicalSha256PatchToMemoryBuffer
+ * missing cnmt, program wrappers
+
+ * romfsWriteFileEntryPatchToMemoryBuffer
+ * calls ncaWriteHierarchicalSha256PatchToMemoryBuffer / ncaWriteHierarchicalIntegrityPatchToMemoryBuffer
+ * missing nacp wrapper
+
+minor steps to take into account:
+
+ * check if rights_id_available == true and titlekey_retrieved == false (preload handling)
+
+
+
+
+
+
+
+
+
todo:
nca: functions for fs section lookup? (could just let the user choose...)
- 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 (otherwise, the console will attempt to connect to the Internet to perform elicense verification before launching the title the ticket belongs to)
tik: automatically dump tickets to the SD card?
tik: use dumped tickets when the original ones can't be found in the ES savefile?