From ebd97d7c8243d630a02a252b9781a5fb46f149d5 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Mon, 12 Oct 2020 16:35:47 -0400 Subject: [PATCH] Add FsAccessControlData to ProgramInfo XML. Also: * Fixed support for NSOs with invalid module_name, api_info, dynstr and/or dynsym offsets/sizes. * Updated the XML generator example to generate XML data for all available NCAs, regardless of their ID offset values. --- code_templates/xml_generator.c | 284 +++++++++++++++++++-------------- source/nca.h | 1 + source/npdm.h | 4 +- source/nso.c | 8 +- source/program_info.c | 79 +++++++-- source/utils.c | 3 +- todo.txt | 7 +- 7 files changed, 236 insertions(+), 150 deletions(-) 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