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 freeNcaFsSectionsList(void);
void updateNcaFsSectionsList(NcaUserData *nca_user_data); 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); NX_INLINE bool useUsbHost(void);
static bool waitForGameCard(void); static bool waitForGameCard(void);
@ -258,7 +261,7 @@ static MenuElementOption g_storageMenuElementOption = {
.selected = 0, .selected = 0,
.getter_func = &getOutputStorageOption, .getter_func = &getOutputStorageOption,
.setter_func = &setOutputStorageOption, .setter_func = &setOutputStorageOption,
.options = NULL .options = NULL // Dynamically set
}; };
static MenuElement g_storageMenuElement = { static MenuElement g_storageMenuElement = {
@ -615,18 +618,31 @@ static Menu g_ticketMenu = {
.elements = g_ticketMenuElements .elements = g_ticketMenuElements
}; };
static bool g_ncaMenuRawMode = false; static TitleInfo *g_ncaBasePatchTitleInfo = NULL;
static NcaContext *g_ncaFsSectionsMenuCtx = 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[] = { static MenuElement *g_ncaFsSectionsSubMenuElements[] = {
&(MenuElement){ &(MenuElement){
.str = "start nca fs dump", .str = "start nca fs dump",
.child_menu = NULL, .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, .element_options = NULL,
.userdata = NULL // Dynamically set .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){ &(MenuElement){
.str = "write section image", .str = "write section image",
.child_menu = NULL, .child_menu = NULL,
@ -663,6 +679,9 @@ static Menu g_ncaFsSectionsSubMenu = {
.elements = g_ncaFsSectionsSubMenuElements .elements = g_ncaFsSectionsSubMenuElements
}; };
static bool g_ncaMenuRawMode = false;
static NcaContext *g_ncaFsSectionsMenuCtx = NULL;
static MenuElement **g_ncaFsSectionsMenuElements = NULL; static MenuElement **g_ncaFsSectionsMenuElements = NULL;
// Dynamically populated using g_ncaFsSectionsMenuElements. // Dynamically populated using g_ncaFsSectionsMenuElements.
@ -1011,7 +1030,7 @@ int main(int argc, char *argv[])
break; break;
} }
svcSleepThread(50000000); // 50 ms svcSleepThread(10000000); // 10 ms
} }
if (!g_appletStatus) break; if (!g_appletStatus) break;
@ -1100,8 +1119,7 @@ int main(int argc, char *argv[])
NcaFsSectionContext *nca_fs_ctx = selected_element->userdata; NcaFsSectionContext *nca_fs_ctx = selected_element->userdata;
if (nca_fs_ctx->enabled) if (nca_fs_ctx->enabled)
{ {
// TODO: add sparse / patch checks here updateNcaBasePatchList(&user_app_data, title_info, nca_fs_ctx);
g_ncaFsSectionsSubMenuElements[0]->userdata = nca_fs_ctx;
} else { } else {
consolePrint("can't dump an invalid nca fs section!\n"); consolePrint("can't dump an invalid nca fs section!\n");
error = true; error = true;
@ -1197,17 +1215,23 @@ int main(int argc, char *argv[])
selected_element_options->selected++; selected_element_options->selected++;
if (!selected_element_options->options[selected_element_options->selected]) 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); 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 } else
if ((btn_down & (HidNpadButton_Left | HidNpadButton_StickLLeft | HidNpadButton_StickRLeft)) && selected_element_options) if ((btn_down & (HidNpadButton_Left | HidNpadButton_StickLLeft | HidNpadButton_StickRLeft)) && selected_element_options)
{ {
selected_element_options->selected--; selected_element_options->selected--;
if (selected_element_options->selected == UINT32_MAX) selected_element_options->selected = 0; 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); 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) if (cur_menu->id == MenuId_UserTitles)
{ {
app_metadata = NULL; app_metadata = NULL;
@ -1241,7 +1265,7 @@ int main(int argc, char *argv[])
} else } else
if (cur_menu->id == MenuId_NcaFsSectionsSubMenu) if (cur_menu->id == MenuId_NcaFsSectionsSubMenu)
{ {
g_ncaFsSectionsSubMenuElements[0]->userdata = NULL; freeNcaBasePatchList();
} }
cur_menu->selected = 0; cur_menu->selected = 0;
@ -1283,7 +1307,7 @@ int main(int argc, char *argv[])
break; 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: end:
@ -1331,7 +1355,7 @@ static void utilsWaitForButtonPress(u64 flag)
{ {
utilsScanPads(); utilsScanPads();
if (utilsGetButtonsDown() & flag) break; if (utilsGetButtonsDown() & flag) break;
svcSleepThread(50000000); // 50 ms svcSleepThread(10000000); // 10 ms
} }
} }
@ -1386,6 +1410,8 @@ void freeStorageList(void)
} }
g_umsDeviceCount = 0; g_umsDeviceCount = 0;
g_storageMenuElementOption.options = NULL;
} }
void updateStorageList(void) void updateStorageList(void)
@ -1414,8 +1440,11 @@ void updateStorageList(void)
u64 total = 0, free = 0; u64 total = 0, free = 0;
char total_str[36] = {0}, free_str[32] = {0}; char total_str[36] = {0}, free_str[32] = {0};
g_storageOptions[idx] = calloc(sizeof(char), 0x300); if (!g_storageOptions[idx])
if (!g_storageOptions[idx]) continue; {
g_storageOptions[idx] = calloc(sizeof(char), 0x300);
if (!g_storageOptions[idx]) continue;
}
if (i == 1) if (i == 1)
{ {
@ -1498,8 +1527,11 @@ void updateTitleList(void)
{ {
TitleApplicationMetadata *cur_app_metadata = app_metadata[i]; TitleApplicationMetadata *cur_app_metadata = app_metadata[i];
g_userTitlesMenuElements[idx] = calloc(1, sizeof(MenuElement)); if (!g_userTitlesMenuElements[idx])
if (!g_userTitlesMenuElements[idx]) continue; {
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]->str = cur_app_metadata->lang_entry.name;
g_userTitlesMenuElements[idx]->child_menu = &g_userTitlesSubMenu; g_userTitlesMenuElements[idx]->child_menu = &g_userTitlesSubMenu;
@ -1565,8 +1597,11 @@ void updateNcaList(TitleInfo *title_info)
u64 nca_size = 0; u64 nca_size = 0;
NcaUserData *nca_user_data = NULL; NcaUserData *nca_user_data = NULL;
g_ncaMenuElements[idx] = calloc(1, sizeof(MenuElement)); if (!g_ncaMenuElements[idx])
if (!g_ncaMenuElements[idx]) continue; {
g_ncaMenuElements[idx] = calloc(1, sizeof(MenuElement));
if (!g_ncaMenuElements[idx]) continue;
}
nca_info_str = calloc(128, sizeof(char)); nca_info_str = calloc(128, sizeof(char));
nca_user_data = calloc(1, sizeof(NcaUserData)); 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]); NcaFsSectionContext *cur_nca_fs_ctx = &(g_ncaFsSectionsMenuCtx->fs_ctx[i]);
char *nca_fs_info_str = NULL; char *nca_fs_info_str = NULL;
g_ncaFsSectionsMenuElements[idx] = calloc(1, sizeof(MenuElement)); if (!g_ncaFsSectionsMenuElements[idx])
if (!g_ncaFsSectionsMenuElements[idx]) continue; {
g_ncaFsSectionsMenuElements[idx] = calloc(1, sizeof(MenuElement));
if (!g_ncaFsSectionsMenuElements[idx]) continue;
}
nca_fs_info_str = calloc(128, sizeof(char)); nca_fs_info_str = calloc(128, sizeof(char));
if (!nca_fs_info_str) continue; if (!nca_fs_info_str) continue;
@ -1700,6 +1738,114 @@ void updateNcaFsSectionsList(NcaUserData *nca_user_data)
g_ncaFsSectionsMenu.elements = g_ncaFsSectionsMenuElements; 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) NX_INLINE bool useUsbHost(void)
{ {
return (g_storageMenuElementOption.selected == 1); return (g_storageMenuElementOption.selected == 1);
@ -2540,7 +2686,7 @@ static bool saveNintendoSubmissionPackage(void *userdata)
utilsCreateThread(&dump_thread, nspThreadFunc, &nsp_thread_data, 2); utilsCreateThread(&dump_thread, nspThreadFunc, &nsp_thread_data, 2);
/* Wait until the background thread calculates the NSP size. */ /* 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) 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)); consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, nsp_thread_data.total_size, percent, (now - start));
consoleRefresh(); consoleRefresh();
svcSleepThread(50000000); // 50 ms svcSleepThread(10000000); // 10 ms
} }
consolePrint("\nwaiting for thread to join\n"); 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)); consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_thread_data->total_size, percent, (now - start));
consoleRefresh(); consoleRefresh();
svcSleepThread(50000000); // 50 ms svcSleepThread(10000000); // 10 ms
} }
consolePrint("\nwaiting for threads to join\n"); 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(). /// Frees data populated by titleGetUserApplicationData().
void titleFreeUserApplicationData(TitleUserApplicationData *user_app_data); 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. /// 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. /// Orphan titles are patches or add-on contents with no NsApplicationControlData available for their parent user application ID.
bool titleAreOrphanTitlesAvailable(void); bool titleAreOrphanTitlesAvailable(void);
@ -158,6 +164,14 @@ const char *titleGetNcmContentMetaTypeName(u8 content_meta_type);
/// Miscellaneous functions. /// 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) NX_INLINE u64 titleGetPatchIdByApplicationId(u64 app_id)
{ {
return (app_id + TITLE_PATCH_ID_OFFSET); return (app_id + TITLE_PATCH_ID_OFFSET);

View file

@ -557,7 +557,8 @@ static bool titleRefreshGameCardTitleInfo(void);
static bool titleIsUserApplicationContentAvailable(u64 app_id); static bool titleIsUserApplicationContentAvailable(u64 app_id);
static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_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 titleSystemTitleMetadataEntrySortFunction(const void *a, const void *b);
static int titleUserApplicationMetadataEntrySortFunction(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); TitleInfo *title_info = (g_titleInterfaceInit ? _titleGetInfoFromStorageByTitleId(storage_id, title_id) : NULL);
if (title_info) 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); 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) \ #define TITLE_ALLOCATE_USER_APP_DATA(elem, msg, decl) \
if (elem##_info && !out->elem##_info) { \ 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) { \ if (!out->elem##_info) { \
LOG_MSG_ERROR("Failed to duplicate %s info for %016lX!", msg, app_id); \ LOG_MSG_ERROR("Failed to duplicate %s info for %016lX!", msg, app_id); \
decl; \ decl; \
@ -926,6 +927,73 @@ void titleFreeUserApplicationData(TitleUserApplicationData *user_app_data)
titleFreeTitleInfo(&(user_app_data->aoc_patch_info)); 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 titleAreOrphanTitlesAvailable(void)
{ {
bool ret = false; bool ret = false;
@ -956,7 +1024,7 @@ TitleInfo **titleGetOrphanTitles(u32 *out_count)
/* Duplicate orphan title info entries. */ /* Duplicate orphan title info entries. */
for(u32 i = 0; i < g_orphanTitleInfoCount; i++) 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]) if (!orphan_info[i])
{ {
LOG_MSG_ERROR("Failed to duplicate info for orphan title %016lX!", g_orphanTitleInfo[i]->meta_key.id); 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; 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 || \ if (!titleIsValidInfoBlock(title_info))
(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)
{ {
LOG_MSG_ERROR("Invalid parameters!"); LOG_MSG_ERROR("Invalid parameters!");
return NULL; return NULL;
} }
TitleInfo *title_info_dup = NULL, *tmp1 = NULL, *tmp2 = NULL; TitleInfo *title_info_dup = NULL, *tmp1 = NULL, *tmp2 = NULL;
NcmContentInfo *content_infos_dup = NULL;
bool dup_previous = false, dup_next = false, success = false; bool dup_previous = false, dup_next = false, success = false;
/* Allocate memory for the new TitleInfo element. */ /* Duplicate TitleInfo object. */
title_info_dup = calloc(1, sizeof(TitleInfo)); title_info_dup = titleDuplicateTitleInfo(title_info);
if (!title_info_dup) if (!title_info_dup)
{ {
LOG_MSG_ERROR("Failed to allocate memory for TitleInfo duplicate!"); LOG_MSG_ERROR("Failed to duplicate TitleInfo object!");
return NULL; 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) \ #define TITLE_DUPLICATE_LINKED_LIST(elem, prv, nxt) \
if (title_info->elem) { \ if (title_info->elem) { \
if (elem) { \ if (elem) { \
title_info_dup->elem = elem; \ title_info_dup->elem = elem; \
} else { \ } 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; \ if (!title_info_dup->elem) goto end; \
dup_##elem = true; \ dup_##elem = true; \
} \ } \
@ -2495,29 +2542,83 @@ static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *prev
end: 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. */ /* 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. */ /* So we'll take care of freeing data the old fashioned way. */
if (!success && title_info_dup)
{
/* Free content infos pointer. */
if (title_info_dup->content_infos) free(title_info_dup->content_infos);
/* 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. */
/* To avoid issues, we'll just clear all linked list pointers. */
TITLE_FREE_DUPLICATED_LINKED_LIST(previous);
TITLE_FREE_DUPLICATED_LINKED_LIST(next);
/* Free allocated buffer and update return pointer. */
free(title_info_dup);
title_info_dup = NULL;
}
#undef TITLE_DUPLICATE_LINKED_LIST
#undef TITLE_FREE_DUPLICATED_LINKED_LIST
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 (!success)
{ {
if (content_infos_dup) free(content_infos_dup); if (content_infos_dup) free(content_infos_dup);
if (title_info_dup) 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. */
/* To avoid issues, we'll just clear all linked list pointers. */
TITLE_FREE_DUPLICATED_LINKED_LIST(previous);
TITLE_FREE_DUPLICATED_LINKED_LIST(next);
/* Free allocated buffer and update return pointer. */
free(title_info_dup); free(title_info_dup);
title_info_dup = NULL; title_info_dup = NULL;
} }
} }
#undef TITLE_DUPLICATE_LINKED_LIST
#undef TITLE_FREE_DUPLICATED_LINKED_LIST
return title_info_dup; return title_info_dup;
} }