1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-29 13:42:11 +00:00

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.
This commit is contained in:
Pablo Curiel 2020-10-12 16:35:47 -04:00
parent 4ab8f44003
commit ebd97d7c82
7 changed files with 236 additions and 150 deletions

View file

@ -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);

View file

@ -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 {

View file

@ -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)

View file

@ -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)))

View file

@ -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, \
" <FsAccessControlData />\n" \
" <History />\n" /* Impossible to get. */ \
"</ProgramInfo>"))) 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, " <FsAccessControlData>\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, \
" <SaveDataOwnerIds>\n" \
" <Accessibility>%s</Accessibility>\n" \
" <Id>0x%016lx</Id>\n" \
" </SaveDataOwnerIds>\n", \
g_facAccessibilityStrings[save_data_owner_block->accessibility[i] & 0x3], \
save_data_owner_ids[i])) goto end;
}
success = utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, " </FsAccessControlData>\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, " <FsAccessControlData />\n");
return success;
}

View file

@ -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);

View file

@ -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