diff --git a/code_templates/threaded_usb_bktr_dumper.c b/code_templates/threaded_usb_bktr_dumper.c index 83caf81..835f9c7 100644 --- a/code_templates/threaded_usb_bktr_dumper.c +++ b/code_templates/threaded_usb_bktr_dumper.c @@ -213,10 +213,13 @@ int main(int argc, char *argv[]) goto out; } - u32 app_count = 0, selected_idx = 0; + u32 app_count = 0; TitleApplicationMetadata **app_metadata = NULL; TitleUserApplicationData user_app_data = {0}; + u32 selected_idx = 0, page_size = 30, cur_page = 0; + bool exit_prompt = true; + u8 *buf = NULL; NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL; @@ -268,21 +271,27 @@ int main(int argc, char *argv[]) while(true) { consoleClear(); - printf("select a base title with an available update.\nthe updated romfs will be dumped via usb.\npress b to cancel.\n\n"); + printf("select a base title with an available update.\nthe updated romfs will be dumped via usb.\npress b to exit.\n\n"); + + for(u32 i = cur_page; i < app_count; i++) + { + if (i >= (cur_page + page_size)) break; + printf("%s%s (%016lX)\n", i == selected_idx ? " -> " : " ", app_metadata[i]->lang_entry.name, app_metadata[i]->title_id); + } + + printf("\n"); - for(u32 i = 0; i < app_count; i++) printf("%s%s (%016lX)\n", i == selected_idx ? " -> " : " ", app_metadata[i]->lang_entry.name, app_metadata[i]->title_id); consoleUpdate(NULL); - u64 btn = 0; - + u64 btn_down = 0, btn_held = 0; while(true) { hidScanInput(); + btn_down = utilsHidKeysAllDown(); + btn_held = utilsHidKeysAllHeld(); + if (btn_down || btn_held) break; - btn = utilsHidKeysAllDown(); - if (btn) break; - - if (titleRefreshGameCardTitleInfo()) + if (titleIsGameCardInfoUpdated()) { free(app_metadata); @@ -293,13 +302,12 @@ int main(int argc, char *argv[]) goto out2; } - selected_idx = 0; - + selected_idx = cur_page = 0; break; } } - if (btn & KEY_A) + if (btn_down & KEY_A) { if (!titleGetUserApplicationData(app_metadata[selected_idx]->title_id, &user_app_data) || !user_app_data.app_info || !user_app_data.patch_info) { @@ -310,29 +318,50 @@ int main(int argc, char *argv[]) break; } else - if (btn & KEY_DOWN) + if ((btn_down & KEY_DDOWN) || (btn_held & (KEY_LSTICK_DOWN | KEY_RSTICK_DOWN))) { - if ((selected_idx + 1) < app_count) + selected_idx++; + + if (selected_idx >= app_count) { - selected_idx++; - } else { - selected_idx = 0; + if (btn_down & KEY_DDOWN) + { + selected_idx = cur_page = 0; + } else { + selected_idx = (app_count - 1); + } + } else + if (selected_idx >= (cur_page + page_size)) + { + cur_page += page_size; } } else - if (btn & KEY_UP) + if ((btn_down & KEY_DUP) || (btn_held & (KEY_LSTICK_UP | KEY_RSTICK_UP))) { - if (selected_idx == 0) + selected_idx--; + + if (selected_idx == UINT32_MAX) { - selected_idx = (app_count - 1); - } else { - selected_idx--; + if (btn_down & KEY_DUP) + { + selected_idx = (app_count - 1); + cur_page = (app_count - (app_count % page_size)); + } else { + selected_idx = 0; + } + } else + if (selected_idx < cur_page) + { + cur_page -= page_size; } } else - if (btn & KEY_B) + if (btn_down & KEY_B) { - consolePrint("\nprocess cancelled.\n"); + exit_prompt = false; goto out2; } + + if (btn_held & (KEY_LSTICK_DOWN | KEY_RSTICK_DOWN | KEY_LSTICK_UP | KEY_RSTICK_UP)) svcSleepThread(50000000); // 50 ms } consoleClear(); @@ -469,8 +498,11 @@ int main(int argc, char *argv[]) consolePrint("process completed in %lu seconds\n", start); out2: - consolePrint("press any button to exit\n"); - utilsWaitForButtonPress(KEY_NONE); + if (exit_prompt) + { + consolePrint("press any button to exit\n"); + utilsWaitForButtonPress(KEY_NONE); + } bktrFreeContext(&bktr_ctx); diff --git a/source/title.c b/source/title.c index 36be3bb..7d77712 100644 --- a/source/title.c +++ b/source/title.c @@ -34,7 +34,10 @@ typedef struct { /* Global variables. */ static Mutex g_titleMutex = 0; -static bool g_titleInterfaceInit = false, g_titleGameCardAvailable = false; +static thrd_t g_titleGameCardInfoThread; +static UEvent g_titleGameCardInfoThreadExitEvent = {0}, *g_titleGameCardStatusChangeUserEvent = NULL; + +static bool g_titleInterfaceInit = false, g_titleGameCardInfoThreadCreated = false, g_titleGameCardAvailable = false, g_titleGameCardInfoUpdated = false; static NsApplicationControlData *g_nsAppControlData = NULL; @@ -373,11 +376,15 @@ static void titleCloseNcmStorages(void); static bool titleOpenNcmDatabaseAndStorageFromGameCard(void); static void titleCloseNcmDatabaseAndStorageFromGameCard(void); -static bool titleLoadTitleInfo(void); +static bool titleLoadPersistentStorageTitleInfo(void); static bool titleRetrieveContentMetaKeysFromDatabase(u8 storage_id); static bool titleGetContentInfosFromTitle(u8 storage_id, const NcmContentMetaKey *meta_key, NcmContentInfo **out_content_infos, u32 *out_content_count); -static bool _titleRefreshGameCardTitleInfo(bool lock); +static bool titleCreateGameCardInfoThread(void); +static void titleDestroyGameCardInfoThread(void); +static int titleGameCardInfoThreadFunc(void *arg); + +static bool titleRefreshGameCardTitleInfo(void); static void titleRemoveGameCardTitleInfoEntries(void); static bool titleIsUserApplicationContentAvailable(u64 app_id); @@ -433,19 +440,25 @@ bool titleInitialize(void) } /* Load title info by retrieving content meta keys from available eMMC System, eMMC User and SD card titles. */ - if (!titleLoadTitleInfo()) + if (!titleLoadPersistentStorageTitleInfo()) { - LOGFILE("Failed to load title info!"); + LOGFILE("Failed to load persistent storage title info!"); goto end; } - /* Initial gamecard title info retrieval. */ - _titleRefreshGameCardTitleInfo(false); - - - + /* Create usermode exit event. */ + ueventCreate(&g_titleGameCardInfoThreadExitEvent, true); + /* Retrieve gamecard status change user event. */ + g_titleGameCardStatusChangeUserEvent = gamecardGetStatusChangeUserEvent(); + if (!g_titleGameCardStatusChangeUserEvent) + { + LOGFILE("Failed to retrieve gamecard status change user event!"); + goto end; + } + /* Create gamecard title info thread. */ + if (!(g_titleGameCardInfoThreadCreated = titleCreateGameCardInfoThread())) goto end; /* @@ -554,10 +567,17 @@ void titleExit(void) { mutexLock(&g_titleMutex); + /* Destroy gamecard detection thread. */ + if (g_titleGameCardInfoThreadCreated) + { + titleDestroyGameCardInfoThread(); + g_titleGameCardInfoThreadCreated = false; + } + /* Free title info. */ titleFreeTitleInfo(); - /* Close gamecard ncm database and storage. */ + /* Close gamecard ncm database and storage (if needed). */ titleCloseNcmDatabaseAndStorageFromGameCard(); /* Close eMMC System, eMMC User and SD card ncm storages. */ @@ -627,11 +647,6 @@ NcmContentStorage *titleGetNcmStorageByStorageId(u8 storage_id) return ncm_storage; } -bool titleRefreshGameCardTitleInfo(void) -{ - return _titleRefreshGameCardTitleInfo(true); -} - TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count) { mutexLock(&g_titleMutex); @@ -743,6 +758,15 @@ end: return success; } +bool titleIsGameCardInfoUpdated(void) +{ + mutexLock(&g_titleMutex); + bool ret = g_titleGameCardInfoUpdated; + if (ret) g_titleGameCardInfoUpdated = false; /* Update flag to avoid updating application metadata entries in the caller function if it's not needed. */ + mutexUnlock(&g_titleMutex); + return ret; +} + const char *titleGetNcmContentTypeName(u8 content_type) { u8 idx = (content_type > NcmContentType_DeltaFragment ? (NcmContentType_DeltaFragment + 1) : content_type); @@ -1118,7 +1142,7 @@ static void titleCloseNcmDatabaseAndStorageFromGameCard(void) if (serviceIsActive(&(ncm_storage->s))) ncmContentStorageClose(ncm_storage); } -static bool titleLoadTitleInfo(void) +static bool titleLoadPersistentStorageTitleInfo(void) { /* Return right away if title info has already been retrieved. */ if (g_titleInfo || g_titleInfoCount) return true; @@ -1384,10 +1408,64 @@ end: return success; } -static bool _titleRefreshGameCardTitleInfo(bool lock) +static bool titleCreateGameCardInfoThread(void) { - if (lock) mutexLock(&g_titleMutex); + if (thrd_create(&g_titleGameCardInfoThread, titleGameCardInfoThreadFunc, NULL) != thrd_success) + { + LOGFILE("Failed to create gamecard title info thread!"); + return false; + } + return true; +} + +static void titleDestroyGameCardInfoThread(void) +{ + /* Signal the exit event to terminate the gamecard title info thread. */ + ueventSignal(&g_titleGameCardInfoThreadExitEvent); + + /* Wait for the gamecard title info thread to exit. */ + thrd_join(g_titleGameCardInfoThread, NULL); +} + +static int titleGameCardInfoThreadFunc(void *arg) +{ + (void)arg; + + Result rc = 0; + int idx = 0; + + Waiter gamecard_status_event_waiter = waiterForUEvent(g_titleGameCardStatusChangeUserEvent); + Waiter exit_event_waiter = waiterForUEvent(&g_titleGameCardInfoThreadExitEvent); + + /* Initial gamecard title info retrieval. */ + mutexLock(&g_titleMutex); + titleRefreshGameCardTitleInfo(); + mutexUnlock(&g_titleMutex); + + while(true) + { + /* Wait until an event is triggered. */ + rc = waitMulti(&idx, -1, gamecard_status_event_waiter, exit_event_waiter); + if (R_FAILED(rc)) continue; + + /* Exit event triggered. */ + if (idx == 1) break; + + /* Update gamecard title info. */ + mutexLock(&g_titleMutex); + g_titleGameCardInfoUpdated = titleRefreshGameCardTitleInfo(); + mutexUnlock(&g_titleMutex); + } + + /* Update gamecard flags. */ + g_titleGameCardAvailable = g_titleGameCardInfoUpdated = false; + + return 0; +} + +static bool titleRefreshGameCardTitleInfo(void) +{ TitleApplicationMetadata *tmp_app_metadata = NULL; u32 orig_app_count = g_appMetadataCount, cur_app_count = g_appMetadataCount, gamecard_app_count = 0, gamecard_metadata_count = 0; bool status = false, success = false, cleanup = true; @@ -1487,7 +1565,7 @@ end: /* Update gamecard status. */ g_titleGameCardAvailable = status; - /* Decrease application metadata buffer size if needed. */ + /* Decrease application metadata buffer size (if needed). */ if ((success && g_appMetadataCount < cur_app_count) || (!success && g_appMetadataCount > orig_app_count)) { if (!success) g_appMetadataCount = orig_app_count; @@ -1500,14 +1578,13 @@ end: } } + /* Remove gamecard title info entries and close its ncm database and storage handles (if needed). */ if (cleanup) { titleRemoveGameCardTitleInfoEntries(); titleCloseNcmDatabaseAndStorageFromGameCard(); } - if (lock) mutexUnlock(&g_titleMutex); - return success; } diff --git a/source/title.h b/source/title.h index 02261e0..4e37fce 100644 --- a/source/title.h +++ b/source/title.h @@ -82,10 +82,6 @@ NcmContentMetaDatabase *titleGetNcmDatabaseByStorageId(u8 storage_id); /// Returns a pointer to a ncm storage handle using a NcmStorageId value. NcmContentStorage *titleGetNcmStorageByStorageId(u8 storage_id); -/// Returns true if gamecard title info has been (un)loaded. -/// Suitable for being called between UI updates. -bool titleRefreshGameCardTitleInfo(void); - /// Returns a pointer to a dynamically allocated buffer of pointers to TitleApplicationMetadata entries, as well as their count. The allocated buffer must be freed by the calling function. /// If 'is_system' is true, TitleApplicationMetadata entries from available system titles (NcmStorageId_BuiltInSystem) will be returned. /// Otherwise, TitleApplicationMetadata entries from user applications with available content data (NcmStorageId_Any) will be returned. @@ -100,6 +96,10 @@ TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id); /// Populates a TitleUserApplicationData element using an user application ID. bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out); +/// Returns true if the gamecard title info entries have been updated (e.g. after a new gamecard has been inserted, of after the current one has been taken out). +/// If titleGetApplicationMetadataEntries() has been previously called, its returned buffer should be freed and a new titleGetApplicationMetadataEntries() call should be issued. +bool titleIsGameCardInfoUpdated(void); + /// Returns a pointer to a string holding the name of the provided ncm content type. const char *titleGetNcmContentTypeName(u8 content_type); diff --git a/todo.txt b/todo.txt index 6e70b45..dbf9fe7 100644 --- a/todo.txt +++ b/todo.txt @@ -19,7 +19,6 @@ todo: bktr: filelist generation functions (wrappers for romfs filelist generation functions) - title: move gamecard stuff to another thread? title: more functions for title lookup (filters, patches / aoc, etc.) title: more functions for content lookup (based on id?) title: find a nice way to deal with *true* orphan content (no ns records from parent base game)