1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-26 04:02:11 +00:00

poc: add base/patch selector.

Other changes include:

* title: add titleGetAddOnContentBaseOrPatchList(); add titleIsValidInfoBlock(); rename titleDuplicateTitleInfo() -> titleDuplicateTitleInfoFull(); add non-linked-list aware titleDuplicateTitleInfo().
This commit is contained in:
Pablo Curiel 2023-05-27 20:10:35 +02:00
parent c6a84f68de
commit 0e70eb0912
3 changed files with 330 additions and 69 deletions

View file

@ -147,6 +147,9 @@ static void switchNcaListTitle(Menu *cur_menu, u32 *element_count, TitleInfo *ti
void freeNcaFsSectionsList(void);
void updateNcaFsSectionsList(NcaUserData *nca_user_data);
void freeNcaBasePatchList(void);
void updateNcaBasePatchList(TitleUserApplicationData *user_app_data, TitleInfo *title_info, NcaFsSectionContext *nca_fs_ctx);
NX_INLINE bool useUsbHost(void);
static bool waitForGameCard(void);
@ -258,7 +261,7 @@ static MenuElementOption g_storageMenuElementOption = {
.selected = 0,
.getter_func = &getOutputStorageOption,
.setter_func = &setOutputStorageOption,
.options = NULL
.options = NULL // Dynamically set
};
static MenuElement g_storageMenuElement = {
@ -615,18 +618,31 @@ static Menu g_ticketMenu = {
.elements = g_ticketMenuElements
};
static bool g_ncaMenuRawMode = false;
static NcaContext *g_ncaFsSectionsMenuCtx = NULL;
static TitleInfo *g_ncaBasePatchTitleInfo = NULL;
static char **g_ncaBasePatchOptions = NULL;
static MenuElementOption g_ncaFsSectionsSubMenuBasePatchElementOption = {
.selected = 0,
.getter_func = NULL,
.setter_func = NULL,
.options = NULL // Dynamically set
};
static MenuElement *g_ncaFsSectionsSubMenuElements[] = {
&(MenuElement){
.str = "start nca fs dump",
.child_menu = NULL,
.task_func = NULL,
.task_func = NULL, // TODO: implement nca fs dump function -- additional sparse/patch checks will go here
.element_options = NULL,
.userdata = NULL // Dynamically set
},
// TODO: place base/patch selector here? display selector at runtime?
&(MenuElement){
.str = "use base/patch title",
.child_menu = NULL,
.task_func = NULL,
.element_options = &g_ncaFsSectionsSubMenuBasePatchElementOption,
.userdata = NULL
},
&(MenuElement){
.str = "write section image",
.child_menu = NULL,
@ -663,6 +679,9 @@ static Menu g_ncaFsSectionsSubMenu = {
.elements = g_ncaFsSectionsSubMenuElements
};
static bool g_ncaMenuRawMode = false;
static NcaContext *g_ncaFsSectionsMenuCtx = NULL;
static MenuElement **g_ncaFsSectionsMenuElements = NULL;
// Dynamically populated using g_ncaFsSectionsMenuElements.
@ -1011,7 +1030,7 @@ int main(int argc, char *argv[])
break;
}
svcSleepThread(50000000); // 50 ms
svcSleepThread(10000000); // 10 ms
}
if (!g_appletStatus) break;
@ -1100,8 +1119,7 @@ int main(int argc, char *argv[])
NcaFsSectionContext *nca_fs_ctx = selected_element->userdata;
if (nca_fs_ctx->enabled)
{
// TODO: add sparse / patch checks here
g_ncaFsSectionsSubMenuElements[0]->userdata = nca_fs_ctx;
updateNcaBasePatchList(&user_app_data, title_info, nca_fs_ctx);
} else {
consolePrint("can't dump an invalid nca fs section!\n");
error = true;
@ -1197,17 +1215,23 @@ int main(int argc, char *argv[])
selected_element_options->selected++;
if (!selected_element_options->options[selected_element_options->selected]) selected_element_options->selected--;
if (selected_element_options->setter_func) selected_element_options->setter_func(selected_element_options->selected);
/* Point to the next base/patch title. */
if (cur_menu->id == MenuId_NcaFsSectionsSubMenu && cur_menu->selected == 1 && g_ncaBasePatchTitleInfo && g_ncaBasePatchTitleInfo->next)
g_ncaBasePatchTitleInfo = g_ncaBasePatchTitleInfo->next;
} else
if ((btn_down & (HidNpadButton_Left | HidNpadButton_StickLLeft | HidNpadButton_StickRLeft)) && selected_element_options)
{
selected_element_options->selected--;
if (selected_element_options->selected == UINT32_MAX) selected_element_options->selected = 0;
if (selected_element_options->setter_func) selected_element_options->setter_func(selected_element_options->selected);
} else
if (btn_down & HidNpadButton_B)
{
if (!cur_menu->parent) break;
/* Point to the previous base/patch title. */
if (cur_menu->id == MenuId_NcaFsSectionsSubMenu && cur_menu->selected == 1 && g_ncaBasePatchTitleInfo && g_ncaBasePatchTitleInfo->previous)
g_ncaBasePatchTitleInfo = g_ncaBasePatchTitleInfo->previous;
} else
if ((btn_down & HidNpadButton_B) && cur_menu->parent)
{
if (cur_menu->id == MenuId_UserTitles)
{
app_metadata = NULL;
@ -1241,7 +1265,7 @@ int main(int argc, char *argv[])
} else
if (cur_menu->id == MenuId_NcaFsSectionsSubMenu)
{
g_ncaFsSectionsSubMenuElements[0]->userdata = NULL;
freeNcaBasePatchList();
}
cur_menu->selected = 0;
@ -1283,7 +1307,7 @@ int main(int argc, char *argv[])
break;
}
if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp | HidNpadButton_ZL | HidNpadButton_ZR)) svcSleepThread(50000000); // 50 ms
if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp | HidNpadButton_ZL | HidNpadButton_ZR)) svcSleepThread(40000000); // 40 ms
}
end:
@ -1331,7 +1355,7 @@ static void utilsWaitForButtonPress(u64 flag)
{
utilsScanPads();
if (utilsGetButtonsDown() & flag) break;
svcSleepThread(50000000); // 50 ms
svcSleepThread(10000000); // 10 ms
}
}
@ -1386,6 +1410,8 @@ void freeStorageList(void)
}
g_umsDeviceCount = 0;
g_storageMenuElementOption.options = NULL;
}
void updateStorageList(void)
@ -1414,8 +1440,11 @@ void updateStorageList(void)
u64 total = 0, free = 0;
char total_str[36] = {0}, free_str[32] = {0};
if (!g_storageOptions[idx])
{
g_storageOptions[idx] = calloc(sizeof(char), 0x300);
if (!g_storageOptions[idx]) continue;
}
if (i == 1)
{
@ -1498,8 +1527,11 @@ void updateTitleList(void)
{
TitleApplicationMetadata *cur_app_metadata = app_metadata[i];
if (!g_userTitlesMenuElements[idx])
{
g_userTitlesMenuElements[idx] = calloc(1, sizeof(MenuElement));
if (!g_userTitlesMenuElements[idx]) continue;
}
g_userTitlesMenuElements[idx]->str = cur_app_metadata->lang_entry.name;
g_userTitlesMenuElements[idx]->child_menu = &g_userTitlesSubMenu;
@ -1565,8 +1597,11 @@ void updateNcaList(TitleInfo *title_info)
u64 nca_size = 0;
NcaUserData *nca_user_data = NULL;
if (!g_ncaMenuElements[idx])
{
g_ncaMenuElements[idx] = calloc(1, sizeof(MenuElement));
if (!g_ncaMenuElements[idx]) continue;
}
nca_info_str = calloc(128, sizeof(char));
nca_user_data = calloc(1, sizeof(NcaUserData));
@ -1677,8 +1712,11 @@ void updateNcaFsSectionsList(NcaUserData *nca_user_data)
NcaFsSectionContext *cur_nca_fs_ctx = &(g_ncaFsSectionsMenuCtx->fs_ctx[i]);
char *nca_fs_info_str = NULL;
if (!g_ncaFsSectionsMenuElements[idx])
{
g_ncaFsSectionsMenuElements[idx] = calloc(1, sizeof(MenuElement));
if (!g_ncaFsSectionsMenuElements[idx]) continue;
}
nca_fs_info_str = calloc(128, sizeof(char));
if (!nca_fs_info_str) continue;
@ -1700,6 +1738,114 @@ void updateNcaFsSectionsList(NcaUserData *nca_user_data)
g_ncaFsSectionsMenu.elements = g_ncaFsSectionsMenuElements;
}
void freeNcaBasePatchList(void)
{
/* Free all previously allocated data. */
if (g_ncaBasePatchOptions)
{
/* Skip the first option. */
for(u32 i = 1; g_ncaBasePatchOptions[i]; i++)
{
free(g_ncaBasePatchOptions[i]);
g_ncaBasePatchOptions[i] = NULL;
}
free(g_ncaBasePatchOptions);
g_ncaBasePatchOptions = NULL;
}
g_ncaFsSectionsSubMenuBasePatchElementOption.selected = 0;
g_ncaFsSectionsSubMenuBasePatchElementOption.options = NULL;
g_ncaFsSectionsSubMenuElements[0]->userdata = NULL;
if (g_ncaBasePatchTitleInfo && (g_ncaBasePatchTitleInfo->meta_key.type == NcmContentMetaType_AddOnContent || g_ncaBasePatchTitleInfo->meta_key.type == NcmContentMetaType_DataPatch))
{
titleFreeTitleInfo(&g_ncaBasePatchTitleInfo);
}
g_ncaBasePatchTitleInfo = NULL;
}
void updateNcaBasePatchList(TitleUserApplicationData *user_app_data, TitleInfo *title_info, NcaFsSectionContext *nca_fs_ctx)
{
char **tmp = NULL;
u32 elem_count = 1, idx = 1; // "no" option
TitleInfo *cur_title_info = NULL;
u8 title_type = title_info->meta_key.type;
u8 content_type = nca_fs_ctx->nca_ctx->content_type;
u8 section_type = nca_fs_ctx->section_type;
bool unsupported = false;
/* Free all previously allocated data. */
freeNcaBasePatchList();
/* Only enable base/patch list if we're dealing with supported content types and/or FS section types. */
if (content_type != NcmContentType_Meta && content_type != NcmContentType_Control && section_type != NcaFsSectionType_Invalid && section_type != NcaFsSectionType_PartitionFs)
{
/* Retrieve corresponding TitleInfo linked list for the current title type. */
switch(title_type)
{
case NcmContentMetaType_Application:
g_ncaBasePatchTitleInfo = user_app_data->patch_info;
break;
case NcmContentMetaType_Patch:
g_ncaBasePatchTitleInfo = user_app_data->app_info;
break;
case NcmContentMetaType_AddOnContent:
case NcmContentMetaType_DataPatch:
g_ncaBasePatchTitleInfo = titleGetAddOnContentBaseOrPatchList(title_info);
break;
default:
break;
}
} else {
unsupported = true;
}
/* Calculate element count. */
elem_count += titleGetCountFromInfoBlock(g_ncaBasePatchTitleInfo);
/* Reallocate buffer. */
tmp = realloc(g_ncaBasePatchOptions, (elem_count + 1) * sizeof(char*)); // NULL terminator
g_ncaBasePatchOptions = tmp;
tmp = NULL;
memset(g_ncaBasePatchOptions, 0, (elem_count + 1) * sizeof(char*)); // NULL terminator
/* Set first option. */
g_ncaBasePatchOptions[0] = (unsupported ? "unsupported for this content/section type" : (elem_count < 2 ? "none available" : "no"));
/* Generate base/patch strings. */
cur_title_info = g_ncaBasePatchTitleInfo;
while(cur_title_info)
{
if (!g_ncaBasePatchOptions[idx])
{
g_ncaBasePatchOptions[idx] = calloc(sizeof(char), 0x40);
if (!g_ncaBasePatchOptions[idx])
{
cur_title_info = cur_title_info->next;
continue;
}
}
snprintf(g_ncaBasePatchOptions[idx], 0x40, "%s v%u (v%u.%u) (%s)", titleGetNcmContentMetaTypeName(cur_title_info->meta_key.type), \
cur_title_info->version.value, cur_title_info->version.application_version.release_ver, cur_title_info->version.application_version.private_ver, \
titleGetNcmStorageIdName(cur_title_info->storage_id));
cur_title_info = cur_title_info->next;
idx++;
}
g_ncaFsSectionsSubMenuBasePatchElementOption.options = g_ncaBasePatchOptions;
g_ncaFsSectionsSubMenuElements[0]->userdata = nca_fs_ctx;
}
NX_INLINE bool useUsbHost(void)
{
return (g_storageMenuElementOption.selected == 1);
@ -2540,7 +2686,7 @@ static bool saveNintendoSubmissionPackage(void *userdata)
utilsCreateThread(&dump_thread, nspThreadFunc, &nsp_thread_data, 2);
/* Wait until the background thread calculates the NSP size. */
while(!nsp_thread_data.total_size && !nsp_thread_data.error) svcSleepThread(50000000); // 50 ms
while(!nsp_thread_data.total_size && !nsp_thread_data.error) svcSleepThread(10000000); // 10 ms
if (nsp_thread_data.error)
{
@ -2602,7 +2748,7 @@ static bool saveNintendoSubmissionPackage(void *userdata)
consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, nsp_thread_data.total_size, percent, (now - start));
consoleRefresh();
svcSleepThread(50000000); // 50 ms
svcSleepThread(10000000); // 10 ms
}
consolePrint("\nwaiting for thread to join\n");
@ -3409,7 +3555,7 @@ static bool spanDumpThreads(ThreadFunc read_func, ThreadFunc write_func, void *a
consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_thread_data->total_size, percent, (now - start));
consoleRefresh();
svcSleepThread(50000000); // 50 ms
svcSleepThread(10000000); // 10 ms
}
consolePrint("\nwaiting for threads to join\n");

View file

@ -123,6 +123,12 @@ bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out);
/// Frees data populated by titleGetUserApplicationData().
void titleFreeUserApplicationData(TitleUserApplicationData *user_app_data);
/// Takes an input TitleInfo object with meta type NcmContentMetaType_AddOnContent or NcmContentMetaType_DataPatch.
/// Returns a linked list of TitleInfo elements with title IDs matching the corresponding base/patch title ID, depending on the meta type of the input TitleInfo object.
/// Particularly useful to display add-on-content base/patch titles related to a specific add-on-content (patch) entry.
/// Use titleFreeTitleInfo() to free the returned data.
TitleInfo *titleGetAddOnContentBaseOrPatchList(TitleInfo *title_info);
/// Returns true if orphan titles are available.
/// Orphan titles are patches or add-on contents with no NsApplicationControlData available for their parent user application ID.
bool titleAreOrphanTitlesAvailable(void);
@ -158,6 +164,14 @@ const char *titleGetNcmContentMetaTypeName(u8 content_meta_type);
/// Miscellaneous functions.
NX_INLINE bool titleIsValidInfoBlock(TitleInfo *title_info)
{
return (title_info && title_info->storage_id >= NcmStorageId_GameCard && title_info->storage_id <= NcmStorageId_SdCard && title_info->meta_key.id && \
((title_info->meta_key.type >= NcmContentMetaType_SystemProgram && title_info->meta_key.type <= NcmContentMetaType_BootImagePackageSafe) || \
(title_info->meta_key.type >= NcmContentMetaType_Application && title_info->meta_key.type <= NcmContentMetaType_DataPatch)) && \
title_info->content_count && title_info->content_infos);
}
NX_INLINE u64 titleGetPatchIdByApplicationId(u64 app_id)
{
return (app_id + TITLE_PATCH_ID_OFFSET);

View file

@ -557,7 +557,8 @@ static bool titleRefreshGameCardTitleInfo(void);
static bool titleIsUserApplicationContentAvailable(u64 app_id);
static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id);
static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *previous, TitleInfo *next);
static TitleInfo *titleDuplicateTitleInfoFull(TitleInfo *title_info, TitleInfo *previous, TitleInfo *next);
static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info);
static int titleSystemTitleMetadataEntrySortFunction(const void *a, const void *b);
static int titleUserApplicationMetadataEntrySortFunction(const void *a, const void *b);
@ -787,7 +788,7 @@ TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id)
TitleInfo *title_info = (g_titleInterfaceInit ? _titleGetInfoFromStorageByTitleId(storage_id, title_id) : NULL);
if (title_info)
{
ret = titleDuplicateTitleInfo(title_info, NULL, NULL);
ret = titleDuplicateTitleInfoFull(title_info, NULL, NULL);
if (!ret) LOG_MSG_ERROR("Failed to duplicate title info for %016lX!", title_id);
}
}
@ -847,7 +848,7 @@ bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out)
#define TITLE_ALLOCATE_USER_APP_DATA(elem, msg, decl) \
if (elem##_info && !out->elem##_info) { \
out->elem##_info = titleDuplicateTitleInfo(elem##_info, NULL, NULL); \
out->elem##_info = titleDuplicateTitleInfoFull(elem##_info, NULL, NULL); \
if (!out->elem##_info) { \
LOG_MSG_ERROR("Failed to duplicate %s info for %016lX!", msg, app_id); \
decl; \
@ -926,6 +927,73 @@ void titleFreeUserApplicationData(TitleUserApplicationData *user_app_data)
titleFreeTitleInfo(&(user_app_data->aoc_patch_info));
}
TitleInfo *titleGetAddOnContentBaseOrPatchList(TitleInfo *title_info)
{
TitleInfo *out = NULL;
bool success = false;
SCOPED_LOCK(&g_titleMutex)
{
if (!g_titleInterfaceInit || !titleIsValidInfoBlock(title_info) || (title_info->meta_key.type != NcmContentMetaType_AddOnContent && \
title_info->meta_key.type != NcmContentMetaType_DataPatch))
{
LOG_MSG_ERROR("Invalid parameters!");
break;
}
TitleInfo *aoc_info = NULL, *tmp = NULL;
u64 ref_tid = title_info->meta_key.id;
u64 lookup_tid = (title_info->meta_key.type == NcmContentMetaType_AddOnContent ? titleGetDataPatchIdByAddOnContentId(ref_tid) : titleGetAddOnContentIdByDataPatchId(ref_tid));
bool error = false;
/* Get info for the first add-on content (patch) title matching the lookup title ID. */
aoc_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, lookup_tid);
if (!aoc_info) break;
/* Create our own custom linked list using entries that match our lookup title ID. */
while(aoc_info)
{
/* Check if this entry's title ID matches our lookup title ID. */
if (aoc_info->meta_key.id != lookup_tid)
{
aoc_info = aoc_info->next;
continue;
}
/* Duplicate current entry. */
tmp = titleDuplicateTitleInfo(aoc_info);
if (!tmp)
{
LOG_MSG_ERROR("Failed to duplicate TitleInfo object!");
error = true;
break;
}
/* Update pointer. */
if (out)
{
out->next = tmp;
} else {
out = tmp;
}
tmp = NULL;
/* Proceed onto the next entry. */
aoc_info = aoc_info->next;
}
if (error) break;
/* Update flag. */
success = true;
}
if (!success && out) titleFreeTitleInfo(&out);
return out;
}
bool titleAreOrphanTitlesAvailable(void)
{
bool ret = false;
@ -956,7 +1024,7 @@ TitleInfo **titleGetOrphanTitles(u32 *out_count)
/* Duplicate orphan title info entries. */
for(u32 i = 0; i < g_orphanTitleInfoCount; i++)
{
orphan_info[i] = titleDuplicateTitleInfo(g_orphanTitleInfo[i], NULL, NULL);
orphan_info[i] = titleDuplicateTitleInfoFull(g_orphanTitleInfo[i], NULL, NULL);
if (!orphan_info[i])
{
LOG_MSG_ERROR("Failed to duplicate info for orphan title %016lX!", g_orphanTitleInfo[i]->meta_key.id);
@ -2421,52 +2489,31 @@ static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id)
return out;
}
static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *previous, TitleInfo *next)
static TitleInfo *titleDuplicateTitleInfoFull(TitleInfo *title_info, TitleInfo *previous, TitleInfo *next)
{
if (!title_info || title_info->storage_id < NcmStorageId_GameCard || title_info->storage_id > NcmStorageId_SdCard || !title_info->meta_key.id || \
(title_info->meta_key.type > NcmContentMetaType_BootImagePackageSafe && title_info->meta_key.type < NcmContentMetaType_Application) || \
title_info->meta_key.type > NcmContentMetaType_DataPatch || !title_info->content_count || !title_info->content_infos)
if (!titleIsValidInfoBlock(title_info))
{
LOG_MSG_ERROR("Invalid parameters!");
return NULL;
}
TitleInfo *title_info_dup = NULL, *tmp1 = NULL, *tmp2 = NULL;
NcmContentInfo *content_infos_dup = NULL;
bool dup_previous = false, dup_next = false, success = false;
/* Allocate memory for the new TitleInfo element. */
title_info_dup = calloc(1, sizeof(TitleInfo));
/* Duplicate TitleInfo object. */
title_info_dup = titleDuplicateTitleInfo(title_info);
if (!title_info_dup)
{
LOG_MSG_ERROR("Failed to allocate memory for TitleInfo duplicate!");
LOG_MSG_ERROR("Failed to duplicate TitleInfo object!");
return NULL;
}
/* Copy TitleInfo data. */
memcpy(title_info_dup, title_info, sizeof(TitleInfo));
title_info_dup->previous = title_info_dup->next = NULL;
/* Allocate memory for NcmContentInfo elements. */
content_infos_dup = calloc(title_info->content_count, sizeof(NcmContentInfo));
if (!content_infos_dup)
{
LOG_MSG_ERROR("Failed to allocate memory for NcmContentInfo duplicates!");
goto end;
}
/* Copy NcmContentInfo data. */
memcpy(content_infos_dup, title_info->content_infos, title_info->content_count * sizeof(NcmContentInfo));
/* Update content infos pointer. */
title_info_dup->content_infos = content_infos_dup;
#define TITLE_DUPLICATE_LINKED_LIST(elem, prv, nxt) \
if (title_info->elem) { \
if (elem) { \
title_info_dup->elem = elem; \
} else { \
title_info_dup->elem = titleDuplicateTitleInfo(title_info->elem, prv, nxt); \
title_info_dup->elem = titleDuplicateTitleInfoFull(title_info->elem, prv, nxt); \
if (!title_info_dup->elem) goto end; \
dup_##elem = true; \
} \
@ -2495,12 +2542,11 @@ static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *prev
end:
/* We can't directly use titleFreeTitleInfo() on title_info_dup because some or all of the linked list data may have been provided as function arguments. */
/* So we'll take care of freeing data the old fashioned way. */
if (!success)
if (!success && title_info_dup)
{
if (content_infos_dup) free(content_infos_dup);
/* Free content infos pointer. */
if (title_info_dup->content_infos) free(title_info_dup->content_infos);
if (title_info_dup)
{
/* Free previous and next linked lists (if duplicated). */
/* We need to take care of not freeing the linked lists right away, either because we may have already freed them, or because they may have been passed as arguments. */
/* Furthermore, both the next pointer from the previous sibling and the previous pointer from the next sibling reference our current duplicated entry. */
@ -2512,7 +2558,6 @@ end:
free(title_info_dup);
title_info_dup = NULL;
}
}
#undef TITLE_DUPLICATE_LINKED_LIST
@ -2521,6 +2566,62 @@ end:
return title_info_dup;
}
static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info)
{
if (!titleIsValidInfoBlock(title_info))
{
LOG_MSG_ERROR("Invalid parameters!");
return NULL;
}
TitleInfo *title_info_dup = NULL;
NcmContentInfo *content_infos_dup = NULL;
bool success = false;
/* Allocate memory for the new TitleInfo element. */
title_info_dup = calloc(1, sizeof(TitleInfo));
if (!title_info_dup)
{
LOG_MSG_ERROR("Failed to allocate memory for TitleInfo duplicate!");
return NULL;
}
/* Copy TitleInfo data. */
memcpy(title_info_dup, title_info, sizeof(TitleInfo));
title_info_dup->previous = title_info_dup->next = NULL;
/* Allocate memory for NcmContentInfo elements. */
content_infos_dup = calloc(title_info->content_count, sizeof(NcmContentInfo));
if (!content_infos_dup)
{
LOG_MSG_ERROR("Failed to allocate memory for NcmContentInfo duplicates!");
goto end;
}
/* Copy NcmContentInfo data. */
memcpy(content_infos_dup, title_info->content_infos, title_info->content_count * sizeof(NcmContentInfo));
/* Update content infos pointer. */
title_info_dup->content_infos = content_infos_dup;
/* Update flag. */
success = true;
end:
if (!success)
{
if (content_infos_dup) free(content_infos_dup);
if (title_info_dup)
{
free(title_info_dup);
title_info_dup = NULL;
}
}
return title_info_dup;
}
static int titleSystemTitleMetadataEntrySortFunction(const void *a, const void *b)
{
const TitleApplicationMetadata *app_metadata_1 = *((const TitleApplicationMetadata**)a);