diff --git a/code_templates/xml_generator.c b/code_templates/xml_generator.c
index 61a261e..1e015dc 100644
--- a/code_templates/xml_generator.c
+++ b/code_templates/xml_generator.c
@@ -35,6 +35,17 @@ static void consolePrint(const char *text, ...)
consoleUpdate(NULL);
}
+static void writeFile(void *buf, size_t buf_size, const char *path)
+{
+ FILE *fd = fopen(path, "wb");
+ if (fd)
+ {
+ fwrite(buf, 1, buf_size, fd);
+ fclose(fd);
+ utilsCommitSdCardFileSystemChanges();
+ }
+}
+
int main(int argc, char *argv[])
{
(void)argc;
@@ -65,12 +76,18 @@ int main(int argc, char *argv[])
NcaContext *nca_ctx = NULL;
Ticket tik = {0};
+ u32 meta_idx = 0;
ContentMetaContext cnmt_ctx = {0};
- ProgramInfoContext program_info_ctx = {0};
- NacpContext nacp_ctx = {0};
- LegalInfoContext legal_info_ctx = {0};
- FILE *xml_fd = NULL;
+ u32 program_count = 0, program_idx = 0;
+ ProgramInfoContext *program_info_ctx = NULL;
+
+ u32 control_count = 0, control_idx = 0;
+ NacpContext *nacp_ctx = NULL;
+
+ u32 legal_info_count = 0, legal_info_idx = 0;
+ LegalInfoContext *legal_info_ctx = NULL;
+
char path[FS_MAX_PATH] = {0};
app_metadata = titleGetApplicationMetadataEntries(false, &app_count);
@@ -194,26 +211,80 @@ int main(int argc, char *argv[])
consolePrint("nca ctx calloc succeeded\n");
- u32 meta_idx = (user_app_data.app_info->content_count - 1), program_idx = 0, control_idx = 0, legal_idx = 0;
+ meta_idx = (user_app_data.app_info->content_count - 1);
+
+ program_count = titleGetContentCountByType(user_app_data.app_info, NcmContentType_Program);
+ if (program_count && !(program_info_ctx = calloc(program_count, sizeof(ProgramInfoContext))))
+ {
+ consolePrint("program info ctx calloc failed\n");
+ goto out2;
+ }
+
+ control_count = titleGetContentCountByType(user_app_data.app_info, NcmContentType_Control);
+ if (control_count && !(nacp_ctx = calloc(control_count, sizeof(NacpContext))))
+ {
+ consolePrint("nacp ctx calloc failed\n");
+ goto out2;
+ }
+
+ legal_info_count = titleGetContentCountByType(user_app_data.app_info, NcmContentType_LegalInformation);
+ if (legal_info_count && !(legal_info_ctx = calloc(legal_info_count, sizeof(LegalInfoContext))))
+ {
+ consolePrint("legal info ctx calloc failed\n");
+ goto out2;
+ }
for(u32 i = 0, j = 0; i < user_app_data.app_info->content_count; i++)
{
- if (user_app_data.app_info->content_infos[i].content_type == NcmContentType_Meta) continue;
+ // set meta nca as the last nca
+ NcmContentInfo *content_info = &(user_app_data.app_info->content_infos[i]);
+ if (content_info->content_type == NcmContentType_Meta) continue;
if (!ncaInitializeContext(&(nca_ctx[j]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
- &(user_app_data.app_info->content_infos[i]), &tik))
+ content_info, &tik))
{
- consolePrint("%s nca initialize ctx failed\n", titleGetNcmContentTypeName(user_app_data.app_info->content_infos[i].content_type));
+ consolePrint("%s #%u initialize nca ctx failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset);
goto out2;
}
- if (user_app_data.app_info->content_infos[i].content_type == NcmContentType_Program && user_app_data.app_info->content_infos[i].id_offset == 0) control_idx = j;
+ consolePrint("%s #%u initialize nca ctx succeeded\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset);
- if (user_app_data.app_info->content_infos[i].content_type == NcmContentType_Control && user_app_data.app_info->content_infos[i].id_offset == 0) control_idx = j;
+ switch(content_info->content_type)
+ {
+ case NcmContentType_Program:
+ if (!programInfoInitializeContext(&(program_info_ctx[program_idx]), &(nca_ctx[j])))
+ {
+ consolePrint("initialize program info ctx failed (%s)\n", nca_ctx[j].content_id_str);
+ goto out2;
+ }
+
+ nca_ctx[j].content_type_ctx = &(program_info_ctx[program_idx++]);
+
+ break;
+ case NcmContentType_Control:
+ if (!nacpInitializeContext(&(nacp_ctx[control_idx]), &(nca_ctx[j])))
+ {
+ consolePrint("initialize nacp ctx failed (%s)\n", nca_ctx[j].content_id_str);
+ goto out2;
+ }
+
+ nca_ctx[j].content_type_ctx = &(nacp_ctx[control_idx++]);
+
+ break;
+ case NcmContentType_LegalInformation:
+ if (!legalInfoInitializeContext(&(legal_info_ctx[legal_info_idx]), &(nca_ctx[j])))
+ {
+ consolePrint("initialize legal info ctx failed (%s)\n", nca_ctx[j].content_id_str);
+ goto out2;
+ }
+
+ nca_ctx[j].content_type_ctx = &(legal_info_ctx[legal_info_idx++]);
+
+ break;
+ default:
+ break;
+ }
- if (user_app_data.app_info->content_infos[i].content_type == NcmContentType_LegalInformation && user_app_data.app_info->content_infos[i].id_offset == 0) legal_idx = j;
-
- consolePrint("%s nca initialize ctx succeeded\n", titleGetNcmContentTypeName(user_app_data.app_info->content_infos[i].content_type));
j++;
}
@@ -226,11 +297,6 @@ int main(int argc, char *argv[])
consolePrint("Meta nca initialize ctx succeeded\n");
- mkdir("sdmc:/at_xml", 0777);
-
- sprintf(path, "sdmc:/at_xml/%016lX", app_metadata[selected_idx]->title_id);
- mkdir(path, 0777);
-
if (!cnmtInitializeContext(&cnmt_ctx, &(nca_ctx[meta_idx])))
{
consolePrint("cnmt initialize ctx failed\n");
@@ -239,124 +305,86 @@ int main(int argc, char *argv[])
consolePrint("cnmt initialize ctx succeeded\n");
+ sprintf(path, "sdmc:/at_xml/%016lX", app_metadata[selected_idx]->title_id);
+ utilsCreateDirectoryTree(path, true);
+
if (cnmtGenerateAuthoringToolXml(&cnmt_ctx, nca_ctx, user_app_data.app_info->content_count))
{
consolePrint("cnmt xml succeeded\n");
- sprintf(path, "sdmc:/at_xml/%016lX/%s.cnmt", app_metadata[selected_idx]->title_id, cnmt_ctx.nca_ctx->content_id_str);
-
- xml_fd = fopen(path, "wb");
- if (xml_fd)
- {
- fwrite(cnmt_ctx.raw_data, 1, cnmt_ctx.raw_data_size, xml_fd);
- fclose(xml_fd);
- xml_fd = NULL;
- }
+ //sprintf(path, "sdmc:/at_xml/%016lX/%s.cnmt", app_metadata[selected_idx]->title_id, cnmt_ctx.nca_ctx->content_id_str);
+ //writeFile(cnmt_ctx.raw_data, cnmt_ctx.raw_data_size, path);
sprintf(path, "sdmc:/at_xml/%016lX/%s.cnmt.xml", app_metadata[selected_idx]->title_id, cnmt_ctx.nca_ctx->content_id_str);
-
- xml_fd = fopen(path, "wb");
- if (xml_fd)
- {
- fwrite(cnmt_ctx.authoring_tool_xml, 1, cnmt_ctx.authoring_tool_xml_size, xml_fd);
- fclose(xml_fd);
- xml_fd = NULL;
- }
+ writeFile(cnmt_ctx.authoring_tool_xml, cnmt_ctx.authoring_tool_xml_size, path);
} else {
consolePrint("cnmt xml failed\n");
}
- if (!programInfoInitializeContext(&program_info_ctx, &(nca_ctx[program_idx])))
+ for(u32 i = 0; i < user_app_data.app_info->content_count; i++)
{
- consolePrint("program info initialize ctx failed\n");
- goto out2;
- }
-
- consolePrint("program info initialize ctx succeeded\n");
-
- if (programInfoGenerateAuthoringToolXml(&program_info_ctx))
- {
- consolePrint("program info xml succeeded\n");
+ NcaContext *cur_nca_ctx = &(nca_ctx[i]);
- sprintf(path, "sdmc:/at_xml/%016lX/%s.programinfo.xml", app_metadata[selected_idx]->title_id, program_info_ctx.nca_ctx->content_id_str);
+ if (!cur_nca_ctx->content_type_ctx || cur_nca_ctx->content_type == NcmContentType_Meta) continue;
- xml_fd = fopen(path, "wb");
- if (xml_fd)
+ switch(cur_nca_ctx->content_type)
{
- fwrite(program_info_ctx.authoring_tool_xml, 1, program_info_ctx.authoring_tool_xml_size, xml_fd);
- fclose(xml_fd);
- xml_fd = NULL;
- }
- } else {
- consolePrint("program info xml failed\n");
- }
-
- if (!nacpInitializeContext(&nacp_ctx, &(nca_ctx[control_idx])))
- {
- consolePrint("nacp initialize ctx failed\n");
- goto out2;
- }
-
- consolePrint("nacp initialize ctx succeeded\n");
-
- if (nacpGenerateAuthoringToolXml(&nacp_ctx, user_app_data.app_info->version.value, cnmtGetRequiredTitleVersion(&cnmt_ctx)))
- {
- consolePrint("nacp xml succeeded\n");
-
- sprintf(path, "sdmc:/at_xml/%016lX/%s.nacp", app_metadata[selected_idx]->title_id, nacp_ctx.nca_ctx->content_id_str);
-
- xml_fd = fopen(path, "wb");
- if (xml_fd)
- {
- fwrite(nacp_ctx.data, 1, sizeof(_NacpStruct), xml_fd);
- fclose(xml_fd);
- xml_fd = NULL;
- }
-
- sprintf(path, "sdmc:/at_xml/%016lX/%s.nacp.xml", app_metadata[selected_idx]->title_id, nacp_ctx.nca_ctx->content_id_str);
-
- xml_fd = fopen(path, "wb");
- if (xml_fd)
- {
- fwrite(nacp_ctx.authoring_tool_xml, 1, nacp_ctx.authoring_tool_xml_size, xml_fd);
- fclose(xml_fd);
- xml_fd = NULL;
- }
-
- for(u8 i = 0; i < nacp_ctx.icon_count; i++)
- {
- NacpIconContext *icon_ctx = &(nacp_ctx.icon_ctx[i]);
-
- sprintf(path, "sdmc:/at_xml/%016lX/%s.nx.%s.jpg", app_metadata[selected_idx]->title_id, nacp_ctx.nca_ctx->content_id_str, nacpGetLanguageString(icon_ctx->language));
-
- xml_fd = fopen(path, "wb");
- if (xml_fd)
+ case NcmContentType_Program:
{
- fwrite(icon_ctx->icon_data, 1, icon_ctx->icon_size, xml_fd);
- fclose(xml_fd);
- xml_fd = NULL;
+ ProgramInfoContext *cur_program_info_ctx = (ProgramInfoContext*)cur_nca_ctx->content_type_ctx;
+ if (!programInfoGenerateAuthoringToolXml(cur_program_info_ctx))
+ {
+ consolePrint("program info xml failed (%s | id offset #%u)\n", cur_nca_ctx->content_id_str, cur_nca_ctx->id_offset);
+ goto out2;
+ }
+
+ consolePrint("program info xml succeeded (%s | id offset #%u)\n", cur_nca_ctx->content_id_str, cur_nca_ctx->id_offset);
+
+ sprintf(path, "sdmc:/at_xml/%016lX/%s.programinfo.xml", app_metadata[selected_idx]->title_id, cur_nca_ctx->content_id_str);
+ writeFile(cur_program_info_ctx->authoring_tool_xml, cur_program_info_ctx->authoring_tool_xml_size, path);
+
+ break;
}
+ case NcmContentType_Control:
+ {
+ NacpContext *cur_nacp_ctx = (NacpContext*)cur_nca_ctx->content_type_ctx;
+ if (!nacpGenerateAuthoringToolXml(cur_nacp_ctx, user_app_data.app_info->version.value, cnmtGetRequiredTitleVersion(&cnmt_ctx)))
+ {
+ consolePrint("nacp xml failed (%s | id offset #%u)\n", cur_nca_ctx->content_id_str, cur_nca_ctx->id_offset);
+ goto out2;
+ }
+
+ consolePrint("nacp xml succeeded (%s | id offset #%u)\n", cur_nca_ctx->content_id_str, cur_nca_ctx->id_offset);
+
+ //sprintf(path, "sdmc:/at_xml/%016lX/%s.nacp", app_metadata[selected_idx]->title_id, cur_nca_ctx->content_id_str);
+ //writeFile(cur_nacp_ctx->data, sizeof(_NacpStruct), path);
+
+ sprintf(path, "sdmc:/at_xml/%016lX/%s.nacp.xml", app_metadata[selected_idx]->title_id, cur_nca_ctx->content_id_str);
+ writeFile(cur_nacp_ctx->authoring_tool_xml, cur_nacp_ctx->authoring_tool_xml_size, path);
+
+ for(u8 j = 0; j < cur_nacp_ctx->icon_count; j++)
+ {
+ NacpIconContext *icon_ctx = &(cur_nacp_ctx->icon_ctx[j]);
+ sprintf(path, "sdmc:/at_xml/%016lX/%s.nx.%s.jpg", app_metadata[selected_idx]->title_id, cur_nca_ctx->content_id_str, nacpGetLanguageString(icon_ctx->language));
+ writeFile(icon_ctx->icon_data, icon_ctx->icon_size, path);
+ }
+
+ break;
+ }
+ case NcmContentType_LegalInformation:
+ {
+ LegalInfoContext *cur_legal_info_ctx = (LegalInfoContext*)cur_nca_ctx->content_type_ctx;
+
+ sprintf(path, "sdmc:/at_xml/%016lX/%s.legalinfo.xml", app_metadata[selected_idx]->title_id, cur_nca_ctx->content_id_str);
+ writeFile(cur_legal_info_ctx->authoring_tool_xml, cur_legal_info_ctx->authoring_tool_xml_size, path);
+
+ consolePrint("legal info xml succeeded (%s | id offset #%u)\n", cur_nca_ctx->content_id_str, cur_nca_ctx->id_offset);
+
+ break;
+ }
+ default:
+ break;
}
- } else {
- consolePrint("nacp xml failed\n");
- }
-
- if (!legalInfoInitializeContext(&legal_info_ctx, &(nca_ctx[legal_idx])))
- {
- consolePrint("legalinfo initialize ctx failed\n");
- goto out2;
- }
-
- consolePrint("legalinfo initialize ctx succeeded\n");
-
- sprintf(path, "sdmc:/at_xml/%016lX/%s.legalinfo.xml", app_metadata[selected_idx]->title_id, legal_info_ctx.nca_ctx->content_id_str);
-
- xml_fd = fopen(path, "wb");
- if (xml_fd)
- {
- fwrite(legal_info_ctx.authoring_tool_xml, 1, legal_info_ctx.authoring_tool_xml_size, xml_fd);
- fclose(xml_fd);
- xml_fd = NULL;
}
out2:
@@ -366,11 +394,23 @@ out2:
utilsWaitForButtonPress(KEY_NONE);
}
- legalInfoFreeContext(&legal_info_ctx);
+ if (legal_info_ctx)
+ {
+ for(u32 i = 0; i < legal_info_count; i++) legalInfoFreeContext(&(legal_info_ctx[i]));
+ free(legal_info_ctx);
+ }
- nacpFreeContext(&nacp_ctx);
+ if (nacp_ctx)
+ {
+ for(u32 i = 0; i < control_count; i++) nacpFreeContext(&(nacp_ctx[i]));
+ free(nacp_ctx);
+ }
- programInfoFreeContext(&program_info_ctx);
+ if (program_info_ctx)
+ {
+ for(u32 i = 0; i < program_count; i++) programInfoFreeContext(&(program_info_ctx[i]));
+ free(program_info_ctx);
+ }
cnmtFreeContext(&cnmt_ctx);
diff --git a/source/nca.h b/source/nca.h
index ff0a245..8db0478 100644
--- a/source/nca.h
+++ b/source/nca.h
@@ -318,6 +318,7 @@ typedef struct {
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];
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.
} NcaContext;
typedef struct {
diff --git a/source/npdm.h b/source/npdm.h
index c20f4ad..a0d2e5a 100644
--- a/source/npdm.h
+++ b/source/npdm.h
@@ -195,9 +195,9 @@ typedef struct {
u8 version;
u8 reserved_1[0x3];
u64 flags;
- u32 content_owner_info_offset;
+ u32 content_owner_info_offset; ///< Relative to the start of this block. Only valid if 'content_owner_info_size' is greater than 0.
u32 content_owner_info_size;
- u32 save_data_owner_info_offset;
+ 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;
#pragma pack(pop)
diff --git a/source/nso.c b/source/nso.c
index 1fa19be..9f75a43 100644
--- a/source/nso.c
+++ b/source/nso.c
@@ -105,25 +105,21 @@ bool nsoInitializeContext(NsoContext *out, PartitionFileSystemContext *pfs_ctx,
if (out->nso_header.module_name_offset < sizeof(NsoHeader) || !out->nso_header.module_name_size || (out->nso_header.module_name_offset + out->nso_header.module_name_size) > pfs_entry->size)
{
LOGFILE("Invalid module name offset/size for NSO \"%s\"! (0x%08X, 0x%08X).", out->nso_filename, out->nso_header.module_name_offset, out->nso_header.module_name_size);
- goto end;
}
if (out->nso_header.api_info_section_header.size && (out->nso_header.api_info_section_header.offset + out->nso_header.api_info_section_header.size) > out->nso_header.rodata_segment_header.size)
{
LOGFILE("Invalid .api_info section offset/size for NSO \"%s\"! (0x%08X, 0x%08X).", out->nso_filename, out->nso_header.api_info_section_header.offset, out->nso_header.api_info_section_header.size);
- goto end;
}
if (!out->nso_header.dynstr_section_header.size || (out->nso_header.dynstr_section_header.offset + out->nso_header.dynstr_section_header.size) > out->nso_header.rodata_segment_header.size)
{
LOGFILE("Invalid .dynstr section offset/size for NSO \"%s\"! (0x%08X, 0x%08X).", out->nso_filename, out->nso_header.dynstr_section_header.offset, out->nso_header.dynstr_section_header.size);
- goto end;
}
if (!out->nso_header.dynsym_section_header.size || (out->nso_header.dynsym_section_header.offset + out->nso_header.dynsym_section_header.size) > out->nso_header.rodata_segment_header.size)
{
LOGFILE("Invalid .dynsym section offset/size for NSO \"%s\"! (0x%08X, 0x%08X).", out->nso_filename, out->nso_header.dynsym_section_header.offset, out->nso_header.dynsym_section_header.size);
- goto end;
}
/* Get module name. */
@@ -159,7 +155,7 @@ end:
static bool nsoGetModuleName(NsoContext *nso_ctx)
{
- if (nso_ctx->nso_header.module_name_size <= 1) return true;
+ if (nso_ctx->nso_header.module_name_offset < sizeof(NsoHeader) || nso_ctx->nso_header.module_name_size <= 1) return true;
NsoModuleName module_name = {0};
@@ -281,7 +277,7 @@ static bool nsoGetModuleInfoName(NsoContext *nso_ctx, u8 *rodata_buf)
static bool nsoGetSectionFromRodataSegment(NsoContext *nso_ctx, u8 *rodata_buf, u8 **section_ptr, u64 section_offset, u64 section_size)
{
- if (!section_size) return true;
+ if (!section_size || (section_offset + section_size) > nso_ctx->nso_header.rodata_segment_header.size) return true;
/* Allocate memory for the desired .rodata section. */
if (!(*section_ptr = malloc(section_size)))
diff --git a/source/program_info.c b/source/program_info.c
index 2ea6cae..3398f6c 100644
--- a/source/program_info.c
+++ b/source/program_info.c
@@ -31,6 +31,13 @@ static const char *g_trueString = "True", *g_falseString = "False";
static const char g_nnSdkString[] = "NintendoSdk_nnSdk";
static const size_t g_nnSdkStringLength = (MAX_ELEMENTS(g_nnSdkString) - 1);
+static const char *g_facAccessibilityStrings[] = {
+ "None",
+ "Read",
+ "Write",
+ "ReadWrite"
+};
+
/* Function prototypes. */
static bool programInfoGetSdkVersionAndBuildTypeFromSdkNso(ProgramInfoContext *program_info_ctx, char **sdk_version, char **build_type);
@@ -43,6 +50,8 @@ static bool programInfoAddStringFieldToAuthoringToolXml(char **xml_buf, u64 *xml
static bool programInfoAddNsoSymbolsToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, ProgramInfoContext *program_info_ctx);
static bool programInfoIsElfSymbolValid(u8 *dynsym_ptr, char *dynstr_base_ptr, u64 dynstr_size, bool is_64bit, char **symbol_str);
+static bool programInfoAddFsAccessControlDataToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, ProgramInfoContext *program_info_ctx);
+
bool programInfoInitializeContext(ProgramInfoContext *out, NcaContext *nca_ctx)
{
if (!out || !nca_ctx || !strlen(nca_ctx->content_id_str) || nca_ctx->content_type != NcmContentType_Program || nca_ctx->content_size < NCA_FULL_HEADER_LENGTH || \
@@ -226,31 +235,19 @@ bool programInfoGenerateAuthoringToolXml(ProgramInfoContext *program_info_ctx)
/* PrivateApiList. */
if (!programInfoAddNsoApiListToAuthoringToolXml(&xml_buf, &xml_buf_size, program_info_ctx, "PrivateApi", "Api", "SDK Private")) goto end;
- /* UnresolvedApiList. Add symbols from "main" NSO. */
+ /* UnresolvedApiList. */
if (!programInfoAddNsoSymbolsToAuthoringToolXml(&xml_buf, &xml_buf_size, program_info_ctx)) goto end;
/* GuidelineList. */
if (!programInfoAddNsoApiListToAuthoringToolXml(&xml_buf, &xml_buf_size, program_info_ctx, "GuidelineApi", "Api", "SDK Guideline")) goto end;
-
-
-
-
-
+ /* FsAccessControlData. */
+ if (!programInfoAddFsAccessControlDataToAuthoringToolXml(&xml_buf, &xml_buf_size, program_info_ctx)) goto end;
if (!(success = utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
- " \n" \
" \n" /* Impossible to get. */ \
""))) goto end;
-
-
-
-
-
-
-
-
/* Update ProgramInfo context. */
program_info_ctx->authoring_tool_xml = xml_buf;
program_info_ctx->authoring_tool_xml_size = strlen(xml_buf);
@@ -552,3 +549,55 @@ static bool programInfoIsElfSymbolValid(u8 *dynsym_ptr, char *dynstr_base_ptr, u
return is_valid;
}
+
+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;
+ u64 *save_data_owner_ids = NULL;
+ bool success = false, fac_data_available = false;
+
+ if (!xml_buf || !xml_buf_size || !program_info_ctx || !(aci_fac_descriptor = program_info_ctx->npdm_ctx.aci_fac_descriptor))
+ {
+ 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;
+
+ /* 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);
+ if (!save_data_owner_block->save_data_owner_id_count)
+ {
+ fac_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));
+
+ if (!utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, " \n")) goto end;
+
+ /* Append save data owner IDs. */
+ for(u32 i = 0; i < save_data_owner_block->save_data_owner_id_count; i++)
+ {
+ if (!utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, \
+ " \n" \
+ " %s\n" \
+ " 0x%016lx\n" \
+ " \n", \
+ g_facAccessibilityStrings[save_data_owner_block->accessibility[i] & 0x3], \
+ save_data_owner_ids[i])) goto end;
+ }
+
+ success = utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, " \n");
+
+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, " \n");
+
+ return success;
+}
diff --git a/source/utils.c b/source/utils.c
index cdf6787..8034f0f 100644
--- a/source/utils.c
+++ b/source/utils.c
@@ -383,9 +383,8 @@ void utilsWriteMessageToLogFile(const char *func_name, const char *fmt, ...)
mutexLock(&g_logfileMutex);
va_list args;
- FILE *logfile = NULL;
- logfile = fopen(LOGFILE_PATH, "a+");
+ FILE *logfile = fopen(LOGFILE_PATH, "a+");
if (!logfile) goto end;
time_t now = time(NULL);
diff --git a/todo.txt b/todo.txt
index ef6d9b4..5e09291 100644
--- a/todo.txt
+++ b/todo.txt
@@ -8,7 +8,7 @@ todo:
tik: automatically dump tickets to the SD card?
tik: use dumped tickets when the original ones can't be found in the ES savefile?
- gamecard: hfs0 functions to display filelist
+ gamecard: functions to display filelist
pfs0: functions to display filelist
pfs0: full header aligned to 0x20 (nsp)
@@ -17,7 +17,8 @@ todo:
bktr: functions to display filelist (wrappers for romfs functions tbh)
- title: more functions for title lookup (filters, patches / aoc, etc.)
- title: more functions for content lookup (based on id?)
+ title: more functions for title lookup? (filters, patches / aoc, etc.)
+ title: more functions for content lookup? (based on id)
+ title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles
\ No newline at end of file