diff --git a/code_templates/nxdt_rw_poc.c b/code_templates/nxdt_rw_poc.c index fe39cbdf..44522aaf 100644 --- a/code_templates/nxdt_rw_poc.c +++ b/code_templates/nxdt_rw_poc.c @@ -137,6 +137,26 @@ typedef struct { bool use_layeredfs_dir; } RomFsThreadData; +typedef struct { + bool highlight; + size_t size; + char size_str[0x10]; + struct dirent dt; +} FsBrowserEntry; + +typedef struct { + SharedThreadData shared_thread_data; + FILE *src; +} FsBrowserFileThreadData; + +typedef struct { + SharedThreadData shared_thread_data; + const char *dir_path; + const FsBrowserEntry *entries; + u32 entries_count; + const char *base_out_path; +} FsBrowserHighlightedEntriesThreadData; + /* Function prototypes. */ static void utilsScanPads(void); @@ -202,6 +222,14 @@ static bool saveTicket(void *userdata); static bool saveNintendoContentArchive(void *userdata); static bool saveNintendoContentArchiveFsSection(void *userdata); +static bool browseNintendoContentArchiveFsSection(void *userdata); + +static bool fsBrowser(const char *mount_name, const char *base_out_path); +static bool fsBrowserGetDirEntries(const char *dir_path, FsBrowserEntry **out_entries, u32 *out_entry_count); +static bool fsBrowserDumpFile(const char *dir_path, const FsBrowserEntry *entry, const char *base_out_path); +static bool fsBrowserDumpHighlightedEntries(const char *dir_path, const FsBrowserEntry *entries, u32 entries_count, const char *base_out_path); + +static bool initializeNcaFsContext(void *userdata, u8 *out_section_type, bool *out_use_layeredfs_dir, NcaContext **out_base_patch_nca_ctx, void **out_fs_ctx); static bool saveRawPartitionFsSection(PartitionFileSystemContext *pfs_ctx, bool use_layeredfs_dir); static bool saveExtractedPartitionFsSection(PartitionFileSystemContext *pfs_ctx, bool use_layeredfs_dir); @@ -222,6 +250,10 @@ static void extractedPartitionFsReadThreadFunc(void *arg); static void rawRomFsReadThreadFunc(void *arg); static void extractedRomFsReadThreadFunc(void *arg); +static void fsBrowserFileReadThreadFunc(void *arg); +static void fsBrowserHighlightedEntriesReadThreadFunc(void *arg); +static bool fsBrowserHighlightedEntriesReadThreadLoop(SharedThreadData *shared_thread_data, const char *dir_path, const FsBrowserEntry *entries, u32 entries_count, const char *base_out_path, void *buf1, void *buf2); + static void genericWriteThreadFunc(void *arg); static bool spanDumpThreads(ThreadFunc read_func, ThreadFunc write_func, void *arg); @@ -689,12 +721,19 @@ static MenuElementOption g_ncaFsSectionsSubMenuBasePatchElementOption = { static MenuElement *g_ncaFsSectionsSubMenuElements[] = { &(MenuElement){ - .str = "start nca fs dump", + .str = "start nca fs section dump", .child_menu = NULL, .task_func = &saveNintendoContentArchiveFsSection, .element_options = NULL, .userdata = NULL // Dynamically set }, + &(MenuElement){ + .str = "browse nca fs section", + .child_menu = NULL, + .task_func = &browseNintendoContentArchiveFsSection, + .element_options = NULL, + .userdata = NULL // Dynamically set + }, &(MenuElement){ .str = "use base/patch title", .child_menu = NULL, @@ -921,7 +960,7 @@ static Menu g_rootMenu = { static Mutex g_conMutex = 0, g_fileMutex = 0; static CondVar g_readCondvar = 0, g_writeCondvar = 0; -static char path[FS_MAX_PATH] = {0}; +static char path[FS_MAX_PATH * 2] = {0}; int main(int argc, char *argv[]) { @@ -978,6 +1017,7 @@ int main(int argc, char *argv[]) } consoleClear(); + consolePrint(APP_TITLE " v" APP_VERSION " (" GIT_REV ").\nBuilt on " BUILD_TIMESTAMP ".\n"); consolePrint("______________________________\n\n"); if (cur_menu->parent) consolePrint("press b to go back\n"); @@ -1245,7 +1285,7 @@ int main(int argc, char *argv[]) break; } - if (cur_menu->id > MenuId_Root) + if (cur_menu->id > MenuId_Root && (cur_menu->id != MenuId_NcaFsSectionsSubMenu || cur_menu->selected != 1)) { /* Wait for USB session (if needed). */ if (useUsbHost() && !waitForUsb()) @@ -1268,9 +1308,12 @@ int main(int argc, char *argv[]) selected_element->task_func(selected_element->userdata); } - /* Display prompt. */ - consolePrint("press any button to continue"); - utilsWaitForButtonPress(0); + if (g_appletStatus && (cur_menu->id != MenuId_NcaFsSectionsSubMenu || cur_menu->selected != 1)) + { + /* Display prompt. */ + consolePrint("press any button to continue"); + utilsWaitForButtonPress(0); + } } } else if (((btn_down & HidNpadButton_Down) || (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown))) && element_count) @@ -1314,7 +1357,7 @@ int main(int argc, char *argv[]) if ((btn_down & (HidNpadButton_Right | HidNpadButton_StickLRight | HidNpadButton_StickRRight)) && selected_element_options) { /* Point to the next base/patch title. */ - if (cur_menu->id == MenuId_NcaFsSectionsSubMenu && cur_menu->selected == 1) + if (cur_menu->id == MenuId_NcaFsSectionsSubMenu && cur_menu->selected == 2) { if (selected_element_options->selected == 0 && g_ncaBasePatchTitleInfoBkp) { @@ -1338,7 +1381,7 @@ int main(int argc, char *argv[]) if (selected_element_options->setter_func) selected_element_options->setter_func(selected_element_options->selected); /* Point to the previous base/patch title. */ - if (cur_menu->id == MenuId_NcaFsSectionsSubMenu && cur_menu->selected == 1) + if (cur_menu->id == MenuId_NcaFsSectionsSubMenu && cur_menu->selected == 2) { if (selected_element_options->selected == 0 && g_ncaBasePatchTitleInfo) { @@ -1433,6 +1476,8 @@ int main(int argc, char *argv[]) break; } + if (!g_appletStatus) break; + utilsAppletLoopDelay(); } @@ -1948,7 +1993,7 @@ void freeNcaBasePatchList(void) g_ncaFsSectionsSubMenuBasePatchElementOption.selected = 0; g_ncaFsSectionsSubMenuBasePatchElementOption.options = NULL; - g_ncaFsSectionsSubMenuElements[0]->userdata = NULL; + g_ncaFsSectionsSubMenuElements[0]->userdata = g_ncaFsSectionsSubMenuElements[1]->userdata = NULL; if (g_ncaBasePatchTitleInfo && (g_ncaBasePatchTitleInfo->meta_key.type == NcmContentMetaType_AddOnContent || g_ncaBasePatchTitleInfo->meta_key.type == NcmContentMetaType_DataPatch)) { @@ -2041,7 +2086,7 @@ void updateNcaBasePatchList(TitleUserApplicationData *user_app_data, TitleInfo * g_ncaFsSectionsSubMenuBasePatchElementOption.options = g_ncaBasePatchOptions; - g_ncaFsSectionsSubMenuElements[0]->userdata = nca_fs_ctx; + g_ncaFsSectionsSubMenuElements[0]->userdata = g_ncaFsSectionsSubMenuElements[1]->userdata = nca_fs_ctx; g_ncaUserTitleInfo = title_info; @@ -3265,330 +3310,479 @@ static bool saveNintendoContentArchive(void *userdata) static bool saveNintendoContentArchiveFsSection(void *userdata) { - NcaFsSectionContext *nca_fs_ctx = (NcaFsSectionContext*)userdata; - NcaContext *nca_ctx = (nca_fs_ctx ? nca_fs_ctx->nca_ctx : NULL); + u8 section_type = 0; + bool use_layeredfs_dir = false; + NcaContext *base_patch_nca_ctx = NULL; + void *fs_ctx = NULL; - /* Sanity checks. */ + bool write_raw_section = (bool)getNcaFsWriteRawSectionOption(); + bool success = false; - if (!g_ncaUserTitleInfo || !nca_fs_ctx || !nca_ctx || !nca_fs_ctx->enabled || nca_fs_ctx->section_type > NcaFsSectionType_Nca0RomFs || \ - (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs && g_ncaBasePatchTitleInfo)) - { - consolePrint("invalid nca fs parameters!\n"); - return false; - } + /* Initialize NCA FS section context. */ + if (!initializeNcaFsContext(userdata, §ion_type, &use_layeredfs_dir, &base_patch_nca_ctx, &fs_ctx)) return false; - if (nca_fs_ctx->has_sparse_layer) + /* Perform requested operation. */ + if (section_type == NcaFsSectionType_PartitionFs) { - if (!g_ncaBasePatchTitleInfo) - { - consolePrint("the selected nca fs section holds a sparse storage\na matching patch of at least v%u must be selected\n", nca_ctx->title_version.value); - return false; - } else - if (g_ncaBasePatchTitleInfo->version.value < nca_ctx->title_version.value) - { - consolePrint("the selected patch doesn't meet the sparse storage version requirement!\nv%u < v%u\n", g_ncaBasePatchTitleInfo->version.value, nca_ctx->title_version.value); - return false; - } + PartitionFileSystemContext *pfs_ctx = (PartitionFileSystemContext*)fs_ctx; + success = (write_raw_section ? saveRawPartitionFsSection(pfs_ctx, use_layeredfs_dir) : saveExtractedPartitionFsSection(pfs_ctx, use_layeredfs_dir)); + pfsFreeContext(pfs_ctx); + } else { + RomFileSystemContext *romfs_ctx = (RomFileSystemContext*)fs_ctx; + success = (write_raw_section ? saveRawRomFsSection(romfs_ctx, use_layeredfs_dir) : saveExtractedRomFsSection(romfs_ctx, use_layeredfs_dir)); + romfsFreeContext(romfs_ctx); } - if (nca_fs_ctx->section_type == NcaFsSectionType_PatchRomFs && !g_ncaBasePatchTitleInfo) - { - consolePrint("patch romfs section selected but no base app provided\n"); - return false; - } + /* Free data. */ + free(fs_ctx); + free(base_patch_nca_ctx); - u8 title_type = nca_ctx->title_type; - u8 content_type = nca_ctx->content_type; - u8 section_type = nca_fs_ctx->section_type; + return success; +} - NcmContentInfo *base_patch_content_info = (g_ncaBasePatchTitleInfo ? titleGetContentInfoByTypeAndIdOffset(g_ncaBasePatchTitleInfo, content_type, nca_ctx->id_offset) : NULL); +static bool browseNintendoContentArchiveFsSection(void *userdata) +{ + u8 section_type = 0; + bool use_layeredfs_dir = false; NcaContext *base_patch_nca_ctx = NULL; - NcaFsSectionContext *base_patch_nca_fs_ctx = NULL; + void *fs_ctx = NULL; - PartitionFileSystemContext pfs_ctx = {0}; - RomFileSystemContext romfs_ctx = {0}; + PartitionFileSystemContext *pfs_ctx = NULL; + RomFileSystemContext *romfs_ctx = NULL; - bool write_raw_section = (bool)getNcaFsWriteRawSectionOption(); - bool use_layeredfs_dir = (bool)getNcaFsUseLayeredFsDirOption(); - bool success = false; + NcaFsSectionContext *nca_fs_ctx = NULL; + NcaContext *nca_ctx = NULL; - /* Override LayeredFS flag, if needed. */ - if (use_layeredfs_dir && \ - (title_type == NcmContentMetaType_Unknown || (title_type > NcmContentMetaType_SystemData && title_type < NcmContentMetaType_Application) || \ - (title_type == NcmContentMetaType_SystemProgram && (content_type != NcmContentType_Program || nca_fs_ctx->section_idx != 0)) || \ - (title_type == NcmContentMetaType_SystemData && (content_type != NcmContentType_Data || nca_fs_ctx->section_idx != 0)) || \ - ((title_type == NcmContentMetaType_Application || title_type == NcmContentMetaType_Patch) && (content_type != NcmContentType_Program || nca_fs_ctx->section_idx > 1)) || \ - ((title_type == NcmContentMetaType_AddOnContent || title_type == NcmContentMetaType_DataPatch) && (content_type != NcmContentType_Data || nca_fs_ctx->section_idx != 0)))) - { - consolePrint("layeredfs setting disabled (unsupported by current content/section type combo)\n"); - use_layeredfs_dir = false; - } + u64 title_id = 0; + u8 title_type = 0; - /* Initialize base/patch NCA context, if needed. */ - if (base_patch_content_info) - { - base_patch_nca_ctx = calloc(1, sizeof(NcaContext)); - if (!base_patch_nca_ctx) - { - consolePrint("failed to allocate memory for base/patch nca ctx!\n"); - goto end; - } + char mount_name[DEVOPTAB_MOUNT_NAME_LENGTH] = {0}, subdir[0x20] = {0}, extension[FS_MAX_PATH] = {0}; + char *base_out_path = NULL; - if (!ncaInitializeContext(base_patch_nca_ctx, g_ncaBasePatchTitleInfo->storage_id, (g_ncaBasePatchTitleInfo->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ - &(g_ncaBasePatchTitleInfo->meta_key), base_patch_content_info, NULL)) - { - consolePrint("failed to initialize base/patch nca ctx!\n"); - goto end; - } + bool success = false; - /* Use a matching NCA FS section entry. */ - base_patch_nca_fs_ctx = &(base_patch_nca_ctx->fs_ctx[nca_fs_ctx->section_idx]); - } + /* Initialize NCA FS section context. */ + if (!initializeNcaFsContext(userdata, §ion_type, &use_layeredfs_dir, &base_patch_nca_ctx, &fs_ctx)) goto end; + /* Mount devoptab device. */ if (section_type == NcaFsSectionType_PartitionFs) { - /* Select the right NCA FS section context, depending on the sparse layer flag. */ - NcaFsSectionContext *pfs_nca_fs_ctx = (nca_fs_ctx->has_sparse_layer ? base_patch_nca_fs_ctx : nca_fs_ctx); + pfs_ctx = (PartitionFileSystemContext*)fs_ctx; + nca_fs_ctx = pfs_ctx->nca_fs_ctx; - /* Initialize PartitionFS context. */ - if (!pfsInitializeContext(&pfs_ctx, pfs_nca_fs_ctx)) + snprintf(mount_name, MAX_ELEMENTS(mount_name), "%s", pfs_ctx->is_exefs ? "exefs" : "pfs"); + + if (!devoptabMountPartitionFileSystemDevice(pfs_ctx, mount_name)) { - consolePrint("pfs initialize ctx failed!\n"); + consolePrint("pfs ctx devoptab mount failed!\n"); goto end; } - - success = (write_raw_section ? saveRawPartitionFsSection(&pfs_ctx, use_layeredfs_dir) : saveExtractedPartitionFsSection(&pfs_ctx, use_layeredfs_dir)); } else { - /* Select the right base/patch NCA FS section contexts. */ - NcaFsSectionContext *base_nca_fs_ctx = (section_type == NcaFsSectionType_PatchRomFs ? base_patch_nca_fs_ctx : nca_fs_ctx); - NcaFsSectionContext *patch_nca_fs_ctx = (section_type == NcaFsSectionType_PatchRomFs ? nca_fs_ctx : base_patch_nca_fs_ctx); + romfs_ctx = (RomFileSystemContext*)fs_ctx; + nca_fs_ctx = romfs_ctx->default_storage_ctx->nca_fs_ctx; - /* Initialize RomFS context. */ - if (!romfsInitializeContext(&romfs_ctx, base_nca_fs_ctx, patch_nca_fs_ctx)) + snprintf(mount_name, MAX_ELEMENTS(mount_name), "ncaromfs"); + + if (!devoptabMountRomFileSystemDevice(romfs_ctx, mount_name)) { - consolePrint("romfs initialize ctx failed!\n"); + consolePrint("romfs ctx devoptab mount failed!\n"); goto end; } + } + + /* Generate output base path. */ + nca_ctx = nca_fs_ctx->nca_ctx; + title_id = nca_ctx->title_id; + title_type = nca_ctx->title_type; + + if (use_layeredfs_dir) + { + /* Only use base title IDs if we're dealing with patches. */ + title_id = (title_type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(title_id) : \ + (title_type == NcmContentMetaType_DataPatch ? titleGetAddOnContentIdByDataPatchId(title_id) : title_id)); + + base_out_path = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "exefs"); + } else { + snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Extracted", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); + snprintf(extension, MAX_ELEMENTS(extension), "/%s #%u/%u", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx); - success = (write_raw_section ? saveRawRomFsSection(&romfs_ctx, use_layeredfs_dir) : saveExtractedRomFsSection(&romfs_ctx, use_layeredfs_dir)); + TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); + base_out_path = generateOutputTitleFileName(title_info, subdir, extension); } -end: - romfsFreeContext(&romfs_ctx); + if (!base_out_path) goto end; - pfsFreeContext(&pfs_ctx); + /* Display file browser. */ + success = fsBrowser(mount_name, base_out_path); + /* Unmount devoptab device. */ + devoptabUnmountDevice(mount_name); + +end: + /* Free data. */ + if (base_out_path) free(base_out_path); + if (pfs_ctx) pfsFreeContext(pfs_ctx); + if (romfs_ctx) romfsFreeContext(romfs_ctx); + if (fs_ctx) free(fs_ctx); if (base_patch_nca_ctx) free(base_patch_nca_ctx); + if (!success && g_appletStatus) + { + consolePrint("press any button to continue\n"); + utilsWaitForButtonPress(0); + } + return success; } -static bool saveRawPartitionFsSection(PartitionFileSystemContext *pfs_ctx, bool use_layeredfs_dir) +static bool fsBrowser(const char *mount_name, const char *base_out_path) { - u64 free_space = 0; - - PfsThreadData pfs_thread_data = {0}; - SharedThreadData *shared_thread_data = &(pfs_thread_data.shared_thread_data); - - NcaFsSectionContext *nca_fs_ctx = pfs_ctx->nca_fs_ctx; - NcaContext *nca_ctx = nca_fs_ctx->nca_ctx; + char dir_path[FS_MAX_PATH] = {0}; + size_t dir_path_len = 0; - u64 title_id = nca_ctx->title_id; - u8 title_type = nca_ctx->title_type; + FsBrowserEntry *entries = NULL; + u32 entries_count = 0, depth = 0; - char subdir[0x20] = {0}, *filename = NULL; - u32 dev_idx = g_storageMenuElementOption.selected; + u32 scroll = 0, selected = 0, highlighted = 0, page_size = 20; - bool success = false; + bool success = true; - pfs_thread_data.pfs_ctx = pfs_ctx; - pfs_thread_data.use_layeredfs_dir = use_layeredfs_dir; - shared_thread_data->total_size = pfs_ctx->size; + /* Get root directory entries. */ + snprintf(dir_path, MAX_ELEMENTS(dir_path), "%s:/", mount_name); + dir_path_len = strlen(dir_path); - consolePrint("raw partitionfs section size: 0x%lX\n", pfs_ctx->size); + if (!(success = fsBrowserGetDirEntries(dir_path, &entries, &entries_count))) goto end; - if (use_layeredfs_dir) + while((g_appletStatus = appletMainLoop())) { - /* Only use base title IDs if we're dealing with patches. */ - title_id = (title_type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(title_id) : \ - (title_type == NcmContentMetaType_DataPatch ? titleGetAddOnContentIdByDataPatchId(title_id) : title_id)); - - filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "exefs.nsp"); - } else { - snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Raw", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); - snprintf(path, MAX_ELEMENTS(path), "/%s #%u/%u.nsp", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx); + consoleClear(); - TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); - filename = generateOutputTitleFileName(title_info, subdir, path); - } + consolePrint("press a to enter a directory / dump a file\n"); + consolePrint("press b to %s\n", depth > 0 ? "move back to the parent dir" : "exit the fs browser"); + consolePrint("press r to (un)highlight the selected entry\n"); + consolePrint("press l to invert the current selection\n"); + consolePrint("press zr to highlight all entries\n"); + consolePrint("press zl to unhighlight all entries\n"); + consolePrint("press y to dump the highlighted entries\n"); + consolePrint("use the sticks to scroll faster\n"); + consolePrint("press + to exit\n"); + consolePrint("______________________________\n\n"); - if (!filename) goto end; + consolePrint("entry: %u / %u\n", selected + 1, entries_count); + consolePrint("highlighted: %u / %u\n", highlighted, entries_count); + consolePrint("current path: %s\n", dir_path); + consolePrint("______________________________\n\n"); - if (dev_idx == 1) - { - if (!usbSendFileProperties(shared_thread_data->total_size, filename)) - { - consolePrint("failed to send file properties for \"%s\"!\n", filename); - goto end; - } - } else { - if (!utilsGetFileSystemStatsByPath(filename, NULL, &free_space)) + for(u32 i = scroll; i < entries_count; i++) { - consolePrint("failed to retrieve free space from selected device\n"); - goto end; - } + if (i >= (scroll + page_size)) break; - if (shared_thread_data->total_size >= free_space) - { - consolePrint("dump size exceeds free space\n"); - goto end; - } + FsBrowserEntry *cur_entry = &(entries[i]); - utilsCreateDirectoryTree(filename, false); + consolePrint("%s", i == selected ? " -> " : " "); - if (dev_idx == 0) - { - if (shared_thread_data->total_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(filename)) - { - consolePrint("failed to create concatenation file for \"%s\"!\n", filename); - goto end; - } - } else { - if (g_umsDevices[dev_idx - 2].fs_type < UsbHsFsDeviceFileSystemType_exFAT && shared_thread_data->total_size > FAT32_FILESIZE_LIMIT) + if (cur_entry->highlight) { - consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n"); - goto end; + consolePrintReversedColors("[%c] %s", cur_entry->dt.d_type == DT_DIR ? 'D' : 'F', cur_entry->dt.d_name); + if (cur_entry->dt.d_type == DT_REG) consolePrintReversedColors(" (%s)", cur_entry->size_str); + } else { + consolePrint("[%c] %s", cur_entry->dt.d_type == DT_DIR ? 'D' : 'F', cur_entry->dt.d_name); + if (cur_entry->dt.d_type == DT_REG) consolePrint(" (%s)", cur_entry->size_str); } - } - shared_thread_data->fp = fopen(filename, "wb"); - if (!shared_thread_data->fp) - { - consolePrint("failed to open \"%s\" for writing!\n", filename); - goto end; + consolePrint("\n"); } - setvbuf(shared_thread_data->fp, NULL, _IONBF, 0); - ftruncate(fileno(shared_thread_data->fp), (off_t)shared_thread_data->total_size); - } - - consoleRefresh(); - - success = spanDumpThreads(rawPartitionFsReadThreadFunc, genericWriteThreadFunc, &pfs_thread_data); + if (!entries_count) consolePrint("no elements available!"); - if (success) - { - consolePrint("successfully saved raw partitionfs section as \"%s\"\n", filename); + consolePrint("\n"); consoleRefresh(); - } -end: - if (shared_thread_data->fp) - { - fclose(shared_thread_data->fp); - shared_thread_data->fp = NULL; + u64 btn_down = 0, btn_held = 0; - if (!success && dev_idx != 1) + while((g_appletStatus = appletMainLoop())) { - if (dev_idx == 0) - { - utilsRemoveConcatenationFile(filename); - utilsCommitSdCardFileSystemChanges(); - } else { - remove(filename); - } + utilsScanPads(); + btn_down = utilsGetButtonsDown(); + btn_held = utilsGetButtonsHeld(); + if (btn_down || btn_held) break; + + utilsAppletLoopDelay(); } - } - if (filename) free(filename); + if (!g_appletStatus) break; - return success; -} + if ((btn_down & HidNpadButton_A) && entries_count) + { + FsBrowserEntry *selected_entry = &(entries[selected]); -static bool saveExtractedPartitionFsSection(PartitionFileSystemContext *pfs_ctx, bool use_layeredfs_dir) -{ - u64 data_size = 0; + if (selected_entry->dt.d_type == DT_DIR) + { + /* Change directory. */ + snprintf(dir_path + dir_path_len, MAX_ELEMENTS(dir_path) - dir_path_len, "%s%s", depth > 0 ? "/" : "", selected_entry->dt.d_name); - PfsThreadData pfs_thread_data = {0}; - SharedThreadData *shared_thread_data = &(pfs_thread_data.shared_thread_data); + if (!(success = fsBrowserGetDirEntries(dir_path, &entries, &entries_count))) break; - bool success = false; + /* Update variables. */ + dir_path_len = strlen(dir_path); + scroll = selected = highlighted = 0; + depth++; + } else { + /* Dump file. */ + fsBrowserDumpFile(dir_path, selected_entry, base_out_path); + } + } else + if (btn_down & HidNpadButton_B) + { + if (depth > 0) + { + /* Go back to the parent directory. */ + char *ptr = strrchr(dir_path, '/'); - if (!pfsGetTotalDataSize(pfs_ctx, &data_size)) - { - consolePrint("failed to calculate extracted partitionfs section size!\n"); - goto end; - } + if (depth > 1) + { + *ptr = '\0'; + } else { + *(++ptr) = '\0'; + } - if (!data_size) + if (!(success = fsBrowserGetDirEntries(dir_path, &entries, &entries_count))) break; + + /* Update variables. */ + dir_path_len = strlen(dir_path); + scroll = selected = highlighted = 0; + depth--; + } else { + break; + } + } else + if ((btn_down & HidNpadButton_R) && entries_count) + { + /* (Un)highlight the selected entry. */ + FsBrowserEntry *selected_entry = &(entries[selected]); + selected_entry->highlight ^= 1; + highlighted += (selected_entry->highlight ? 1 : -1); + } else + if ((btn_down & HidNpadButton_L) && entries_count) + { + /* Invert current selection. */ + for(u32 i = 0; i < entries_count; i++) + { + FsBrowserEntry *cur_entry = &(entries[i]); + cur_entry->highlight ^= 1; + highlighted += (cur_entry->highlight ? 1 : -1); + } + } else + if ((btn_down & HidNpadButton_ZR) && entries_count) + { + /* Highlight all entries. */ + for(u32 i = 0; i < entries_count; i++) entries[i].highlight = true; + + /* Update counter. */ + highlighted = entries_count; + } else + if ((btn_down & HidNpadButton_ZL) && entries_count) + { + /* Unhighlight all entries. */ + for(u32 i = 0; i < entries_count; i++) entries[i].highlight = false; + + /* Reset counter. */ + highlighted = 0; + } else + if ((btn_down & HidNpadButton_Y) && entries_count && highlighted) + { + /* Dump highlighted entries. */ + fsBrowserDumpHighlightedEntries(dir_path, entries, entries_count, base_out_path); + + /* Unhighlight all entries. */ + for(u32 i = 0; i < entries_count; i++) entries[i].highlight = false; + + /* Reset counter. */ + highlighted = 0; + } else + if (((btn_down & HidNpadButton_Down) || (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown))) && entries_count) + { + selected++; + + if (selected >= entries_count) + { + if (btn_down & HidNpadButton_Down) + { + scroll = 0; + selected = 0; + } else { + selected--; + } + } else + if (selected >= (scroll + (page_size / 2)) && entries_count > (scroll + page_size)) + { + scroll++; + } + } else + if (((btn_down & HidNpadButton_Up) || (btn_held & (HidNpadButton_StickLUp | HidNpadButton_StickRUp))) && entries_count) + { + selected--; + + if (selected == UINT32_MAX) + { + if (btn_down & HidNpadButton_Up) + { + selected = (entries_count - 1); + scroll = (entries_count >= page_size ? (entries_count - page_size) : 0); + } else { + selected = 0; + } + } else + if (selected < (scroll + (page_size / 2)) && scroll > 0) + { + scroll--; + } + } else + if (btn_down & HidNpadButton_Plus) + { + g_appletStatus = false; + break; + } + + utilsAppletLoopDelay(); + } + +end: + if (entries) free(entries); + + return success; +} + +static bool fsBrowserGetDirEntries(const char *dir_path, FsBrowserEntry **out_entries, u32 *out_entry_count) +{ + DIR *dp = NULL; + struct dirent *dt = NULL; + struct stat st = {0}; + FsBrowserEntry *entries = NULL, *entries_tmp = NULL; + char tmp_path[FS_MAX_PATH] = {0}; + u32 count = 0; + bool append_path_sep = (dir_path[strlen(dir_path) - 1] != '/'); + bool success = false; + + /* Free input pointer, if needed. */ + if (*out_entries) { - consolePrint("partitionfs section is empty!\n"); + free(*out_entries); + *out_entries = NULL; + } + + /* Open directory. */ + dp = opendir(dir_path); + if (!dp) + { + consolePrint("failed to open dir \"%s\"\n", dir_path); goto end; } - pfs_thread_data.pfs_ctx = pfs_ctx; - pfs_thread_data.use_layeredfs_dir = use_layeredfs_dir; - shared_thread_data->total_size = data_size; + /* Get entry count. */ + while((dt = readdir(dp))) + { + /* Skip "." and ".." entries. */ + if (!strcmp(dt->d_name, ".") || !strcmp(dt->d_name, "..") != 0) continue; - consolePrint("extracted partitionfs section size: 0x%lX\n", data_size); - consoleRefresh(); + /* Reallocate directory entries buffer. */ + if (!(entries_tmp = realloc(entries, (count + 1) * sizeof(FsBrowserEntry)))) + { + consolePrint("failed to allocate memory for dir entries in \"%s\"\n", dir_path); + goto end; + } - success = spanDumpThreads(extractedPartitionFsReadThreadFunc, genericWriteThreadFunc, &pfs_thread_data); + entries = entries_tmp; + entries_tmp = NULL; + + /* Store entry data. */ + FsBrowserEntry *cur_entry = &(entries[count++]); + + memset(cur_entry, 0, sizeof(FsBrowserEntry)); + + if (dt->d_type == DT_REG) + { + /* Get file size. */ + snprintf(tmp_path, MAX_ELEMENTS(tmp_path), "%s%s%s", dir_path, append_path_sep ? "/" : "", dt->d_name); + stat(tmp_path, &st); + cur_entry->size = st.st_size; + utilsGenerateFormattedSizeString((double)st.st_size, cur_entry->size_str, sizeof(cur_entry->size_str)); + } + + memcpy(&(cur_entry->dt), dt, sizeof(struct dirent)); + } + + /* Short-circuit: handle empty directories. */ + if (!entries) + { + *out_entry_count = 0; + success = true; + goto end; + } + + /* Update output pointers. */ + *out_entries = entries; + *out_entry_count = count; + + /* Update return value. */ + success = true; end: + if (dp) closedir(dp); + + if (!success && entries) free(entries); + return success; } -static bool saveRawRomFsSection(RomFileSystemContext *romfs_ctx, bool use_layeredfs_dir) +static bool fsBrowserDumpFile(const char *dir_path, const FsBrowserEntry *entry, const char *base_out_path) { u64 free_space = 0; - RomFsThreadData romfs_thread_data = {0}; - SharedThreadData *shared_thread_data = &(romfs_thread_data.shared_thread_data); - - NcaFsSectionContext *nca_fs_ctx = romfs_ctx->default_storage_ctx->nca_fs_ctx; - NcaContext *nca_ctx = nca_fs_ctx->nca_ctx; - - u64 title_id = nca_ctx->title_id; - u8 title_type = nca_ctx->title_type; + FsBrowserFileThreadData fs_browser_thread_data = {0}; + SharedThreadData *shared_thread_data = &(fs_browser_thread_data.shared_thread_data); - char subdir[0x20] = {0}, *filename = NULL; u32 dev_idx = g_storageMenuElementOption.selected; bool success = false; - romfs_thread_data.romfs_ctx = romfs_ctx; - romfs_thread_data.use_layeredfs_dir = use_layeredfs_dir; - shared_thread_data->total_size = romfs_ctx->size; + shared_thread_data->total_size = entry->size; - consolePrint("raw romfs section size: 0x%lX\n", romfs_ctx->size); + snprintf(path, MAX_ELEMENTS(path), "%s%s%s", dir_path, dir_path[strlen(dir_path) - 1] != '/' ? "/" : "", entry->dt.d_name); - if (use_layeredfs_dir) + consoleClear(); + consolePrint("file path: %s\n", path); + consolePrint("file size: 0x%lX\n\n", entry->size); + + /* Open input file. */ + fs_browser_thread_data.src = fopen(path, "rb"); + if (!fs_browser_thread_data.src) { - /* Only use base title IDs if we're dealing with patches. */ - title_id = (title_type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(title_id) : \ - (title_type == NcmContentMetaType_DataPatch ? titleGetAddOnContentIdByDataPatchId(title_id) : title_id)); + consolePrint("failed to open input file!\n"); + goto end; + } - filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "romfs.bin"); - } else { - snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Raw", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); - snprintf(path, MAX_ELEMENTS(path), "/%s #%u/%u.bin", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx); + setvbuf(fs_browser_thread_data.src, NULL, _IONBF, 0); - TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); - filename = generateOutputTitleFileName(title_info, subdir, path); + const char *dir_path_start = (strchr(dir_path, '/') + 1); + if (*dir_path_start) + { + snprintf(path, MAX_ELEMENTS(path), "%s/%s/%s", base_out_path, dir_path_start, entry->dt.d_name); + } else { + snprintf(path, MAX_ELEMENTS(path), "%s/%s", base_out_path, entry->dt.d_name); } - if (!filename) goto end; - if (dev_idx == 1) { - if (!usbSendFileProperties(shared_thread_data->total_size, filename)) + if (!waitForUsb()) goto end; + + if (!usbSendFileProperties(shared_thread_data->total_size, path)) { - consolePrint("failed to send file properties for \"%s\"!\n", filename); + consolePrint("failed to send file properties for \"%s\"!\n", path); goto end; } } else { - if (!utilsGetFileSystemStatsByPath(filename, NULL, &free_space)) + if (!utilsGetFileSystemStatsByPath(path, NULL, &free_space)) { consolePrint("failed to retrieve free space from selected device\n"); goto end; @@ -3600,13 +3794,13 @@ static bool saveRawRomFsSection(RomFileSystemContext *romfs_ctx, bool use_layere goto end; } - utilsCreateDirectoryTree(filename, false); + utilsCreateDirectoryTree(path, false); if (dev_idx == 0) { - if (shared_thread_data->total_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(filename)) + if (shared_thread_data->total_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(path)) { - consolePrint("failed to create concatenation file for \"%s\"!\n", filename); + consolePrint("failed to create concatenation file for \"%s\"!\n", path); goto end; } } else { @@ -3617,24 +3811,916 @@ static bool saveRawRomFsSection(RomFileSystemContext *romfs_ctx, bool use_layere } } - shared_thread_data->fp = fopen(filename, "wb"); - if (!shared_thread_data->fp) - { - consolePrint("failed to open \"%s\" for writing!\n", filename); - goto end; + shared_thread_data->fp = fopen(path, "wb"); + if (!shared_thread_data->fp) + { + consolePrint("failed to open \"%s\" for writing!\n", path); + goto end; + } + + setvbuf(shared_thread_data->fp, NULL, _IONBF, 0); + ftruncate(fileno(shared_thread_data->fp), (off_t)shared_thread_data->total_size); + } + + consoleRefresh(); + + success = spanDumpThreads(fsBrowserFileReadThreadFunc, genericWriteThreadFunc, &fs_browser_thread_data); + + if (success) + { + consolePrint("successfully saved file to \"%s\"\n", path); + consoleRefresh(); + } + +end: + if (shared_thread_data->fp) + { + fclose(shared_thread_data->fp); + shared_thread_data->fp = NULL; + + if (!success && dev_idx != 1) + { + if (dev_idx == 0) + { + utilsRemoveConcatenationFile(path); + utilsCommitSdCardFileSystemChanges(); + } else { + remove(path); + } + } + } + + if (fs_browser_thread_data.src) fclose(fs_browser_thread_data.src); + + consolePrint("press any button to continue\n"); + utilsWaitForButtonPress(0); + + return success; +} + +static bool fsBrowserDumpHighlightedEntries(const char *dir_path, const FsBrowserEntry *entries, u32 entries_count, const char *base_out_path) +{ + bool append_path_sep = (dir_path[strlen(dir_path) - 1] != '/'); + u64 data_size = 0; + + FsBrowserHighlightedEntriesThreadData fs_browser_thread_data = {0}; + SharedThreadData *shared_thread_data = &(fs_browser_thread_data.shared_thread_data); + + bool success = false; + + consoleClear(); + consolePrint("calculating dump size...\n"); + consoleRefresh(); + + /* Calculate dump size. */ + for(u32 i = 0; i < entries_count; i++) + { + const FsBrowserEntry *cur_entry = &(entries[i]); + if (!cur_entry->highlight) continue; + + if (cur_entry->dt.d_type == DT_DIR) + { + /* Get directory size. */ + u64 dir_size = 0; + snprintf(path, MAX_ELEMENTS(path), "%s%s%s", dir_path, append_path_sep ? "/" : "", cur_entry->dt.d_name); + + if (!utilsGetDirectorySize(path, &dir_size)) + { + consolePrint("failed to calculate size for dir \"%s\"\n", path); + goto end; + } + + /* Update dump size. */ + data_size += dir_size; + } else { + /* Update dump size. */ + data_size += cur_entry->size; + } + } + + fs_browser_thread_data.dir_path = dir_path; + fs_browser_thread_data.entries = entries; + fs_browser_thread_data.entries_count = entries_count; + fs_browser_thread_data.base_out_path = base_out_path; + shared_thread_data->total_size = data_size; + + consolePrint("dump size: 0x%lX\n", data_size); + consoleRefresh(); + + success = spanDumpThreads(fsBrowserHighlightedEntriesReadThreadFunc, genericWriteThreadFunc, &fs_browser_thread_data); + +end: + consolePrint("press any button to continue\n"); + utilsWaitForButtonPress(0); + + return success; +} + +static bool initializeNcaFsContext(void *userdata, u8 *out_section_type, bool *out_use_layeredfs_dir, NcaContext **out_base_patch_nca_ctx, void **out_fs_ctx) +{ + NcaFsSectionContext *nca_fs_ctx = (NcaFsSectionContext*)userdata; + NcaContext *nca_ctx = (nca_fs_ctx ? nca_fs_ctx->nca_ctx : NULL); + + /* Sanity checks. */ + + if (!g_ncaUserTitleInfo || !nca_fs_ctx || !nca_ctx || !nca_fs_ctx->enabled || nca_fs_ctx->section_type > NcaFsSectionType_Nca0RomFs || \ + (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs && g_ncaBasePatchTitleInfo)) + { + consolePrint("invalid nca fs parameters!\n"); + return false; + } + + if (nca_fs_ctx->has_sparse_layer) + { + if (!g_ncaBasePatchTitleInfo) + { + consolePrint("the selected nca fs section holds a sparse storage\na matching patch of at least v%u must be selected\n", nca_ctx->title_version.value); + return false; + } else + if (g_ncaBasePatchTitleInfo->version.value < nca_ctx->title_version.value) + { + consolePrint("the selected patch doesn't meet the sparse storage version requirement!\nv%u < v%u\n", g_ncaBasePatchTitleInfo->version.value, nca_ctx->title_version.value); + return false; + } + } + + if (nca_fs_ctx->section_type == NcaFsSectionType_PatchRomFs && !g_ncaBasePatchTitleInfo) + { + consolePrint("patch romfs section selected but no base app provided\n"); + return false; + } + + u8 title_type = nca_ctx->title_type; + u8 content_type = nca_ctx->content_type; + u8 section_type = nca_fs_ctx->section_type; + + NcmContentInfo *base_patch_content_info = (g_ncaBasePatchTitleInfo ? titleGetContentInfoByTypeAndIdOffset(g_ncaBasePatchTitleInfo, content_type, nca_ctx->id_offset) : NULL); + NcaContext *base_patch_nca_ctx = NULL; + NcaFsSectionContext *base_patch_nca_fs_ctx = NULL; + + bool use_layeredfs_dir = (bool)getNcaFsUseLayeredFsDirOption(); + bool success = false; + + /* Override LayeredFS flag, if needed. */ + if (use_layeredfs_dir && \ + (title_type == NcmContentMetaType_Unknown || (title_type > NcmContentMetaType_SystemData && title_type < NcmContentMetaType_Application) || \ + (title_type == NcmContentMetaType_SystemProgram && (content_type != NcmContentType_Program || nca_fs_ctx->section_idx != 0)) || \ + (title_type == NcmContentMetaType_SystemData && (content_type != NcmContentType_Data || nca_fs_ctx->section_idx != 0)) || \ + ((title_type == NcmContentMetaType_Application || title_type == NcmContentMetaType_Patch) && (content_type != NcmContentType_Program || nca_fs_ctx->section_idx > 1)) || \ + ((title_type == NcmContentMetaType_AddOnContent || title_type == NcmContentMetaType_DataPatch) && (content_type != NcmContentType_Data || nca_fs_ctx->section_idx != 0)))) + { + consolePrint("layeredfs setting disabled (unsupported by current content/section type combo)\n"); + use_layeredfs_dir = false; + } + + /* Initialize base/patch NCA context, if needed. */ + if (base_patch_content_info) + { + base_patch_nca_ctx = calloc(1, sizeof(NcaContext)); + if (!base_patch_nca_ctx) + { + consolePrint("failed to allocate memory for base/patch nca ctx!\n"); + goto end; + } + + if (!ncaInitializeContext(base_patch_nca_ctx, g_ncaBasePatchTitleInfo->storage_id, (g_ncaBasePatchTitleInfo->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ + &(g_ncaBasePatchTitleInfo->meta_key), base_patch_content_info, NULL)) + { + consolePrint("failed to initialize base/patch nca ctx!\n"); + goto end; + } + + /* Use a matching NCA FS section entry. */ + base_patch_nca_fs_ctx = &(base_patch_nca_ctx->fs_ctx[nca_fs_ctx->section_idx]); + } + + if (section_type == NcaFsSectionType_PartitionFs) + { + /* Select the right NCA FS section context, depending on the sparse layer flag. */ + NcaFsSectionContext *pfs_nca_fs_ctx = ((title_type == NcmContentMetaType_Application && base_patch_nca_fs_ctx && base_patch_nca_fs_ctx->enabled) ? base_patch_nca_fs_ctx : nca_fs_ctx); + + /* Initialize PartitionFS context. */ + PartitionFileSystemContext *pfs_ctx = calloc(1, sizeof(PartitionFileSystemContext)); + if (!pfs_ctx) + { + consolePrint("pfs ctx alloc failed!\n"); + goto end; + } + + if (!pfsInitializeContext(pfs_ctx, pfs_nca_fs_ctx)) + { + consolePrint("pfs initialize ctx failed!\n"); + free(pfs_ctx); + goto end; + } + + *out_fs_ctx = pfs_ctx; + } else { + /* Select the right base/patch NCA FS section contexts. */ + NcaFsSectionContext *base_nca_fs_ctx = (section_type == NcaFsSectionType_PatchRomFs ? base_patch_nca_fs_ctx : nca_fs_ctx); + NcaFsSectionContext *patch_nca_fs_ctx = (section_type == NcaFsSectionType_PatchRomFs ? nca_fs_ctx : base_patch_nca_fs_ctx); + + /* Initialize RomFS context. */ + RomFileSystemContext *romfs_ctx = calloc(1, sizeof(RomFileSystemContext)); + if (!romfs_ctx) + { + consolePrint("romfs ctx alloc failed!\n"); + goto end; + } + + if (!romfsInitializeContext(romfs_ctx, base_nca_fs_ctx, patch_nca_fs_ctx)) + { + consolePrint("romfs initialize ctx failed!\n"); + free(romfs_ctx); + goto end; + } + + *out_fs_ctx = romfs_ctx; + } + + /* Update output pointers. */ + *out_section_type = section_type; + *out_use_layeredfs_dir = use_layeredfs_dir; + *out_base_patch_nca_ctx = base_patch_nca_ctx; + + success = true; + +end: + if (!success && base_patch_nca_ctx) free(base_patch_nca_ctx); + + return success; +} + +static bool saveRawPartitionFsSection(PartitionFileSystemContext *pfs_ctx, bool use_layeredfs_dir) +{ + u64 free_space = 0; + + PfsThreadData pfs_thread_data = {0}; + SharedThreadData *shared_thread_data = &(pfs_thread_data.shared_thread_data); + + NcaFsSectionContext *nca_fs_ctx = pfs_ctx->nca_fs_ctx; + NcaContext *nca_ctx = nca_fs_ctx->nca_ctx; + + u64 title_id = nca_ctx->title_id; + u8 title_type = nca_ctx->title_type; + + char subdir[0x20] = {0}, *filename = NULL; + u32 dev_idx = g_storageMenuElementOption.selected; + + bool success = false; + + pfs_thread_data.pfs_ctx = pfs_ctx; + pfs_thread_data.use_layeredfs_dir = use_layeredfs_dir; + shared_thread_data->total_size = pfs_ctx->size; + + consolePrint("raw partitionfs section size: 0x%lX\n", pfs_ctx->size); + + if (use_layeredfs_dir) + { + /* Only use base title IDs if we're dealing with patches. */ + title_id = (title_type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(title_id) : \ + (title_type == NcmContentMetaType_DataPatch ? titleGetAddOnContentIdByDataPatchId(title_id) : title_id)); + + filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "exefs.nsp"); + } else { + snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Raw", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); + snprintf(path, MAX_ELEMENTS(path), "/%s #%u/%u.nsp", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx); + + TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); + filename = generateOutputTitleFileName(title_info, subdir, path); + } + + if (!filename) goto end; + + if (dev_idx == 1) + { + if (!usbSendFileProperties(shared_thread_data->total_size, filename)) + { + consolePrint("failed to send file properties for \"%s\"!\n", filename); + goto end; + } + } else { + if (!utilsGetFileSystemStatsByPath(filename, NULL, &free_space)) + { + consolePrint("failed to retrieve free space from selected device\n"); + goto end; + } + + if (shared_thread_data->total_size >= free_space) + { + consolePrint("dump size exceeds free space\n"); + goto end; + } + + utilsCreateDirectoryTree(filename, false); + + if (dev_idx == 0) + { + if (shared_thread_data->total_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(filename)) + { + consolePrint("failed to create concatenation file for \"%s\"!\n", filename); + goto end; + } + } else { + if (g_umsDevices[dev_idx - 2].fs_type < UsbHsFsDeviceFileSystemType_exFAT && shared_thread_data->total_size > FAT32_FILESIZE_LIMIT) + { + consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n"); + goto end; + } + } + + shared_thread_data->fp = fopen(filename, "wb"); + if (!shared_thread_data->fp) + { + consolePrint("failed to open \"%s\" for writing!\n", filename); + goto end; + } + + setvbuf(shared_thread_data->fp, NULL, _IONBF, 0); + ftruncate(fileno(shared_thread_data->fp), (off_t)shared_thread_data->total_size); + } + + consoleRefresh(); + + success = spanDumpThreads(rawPartitionFsReadThreadFunc, genericWriteThreadFunc, &pfs_thread_data); + + if (success) + { + consolePrint("successfully saved raw partitionfs section as \"%s\"\n", filename); + consoleRefresh(); + } + +end: + if (shared_thread_data->fp) + { + fclose(shared_thread_data->fp); + shared_thread_data->fp = NULL; + + if (!success && dev_idx != 1) + { + if (dev_idx == 0) + { + utilsRemoveConcatenationFile(filename); + utilsCommitSdCardFileSystemChanges(); + } else { + remove(filename); + } + } + } + + if (filename) free(filename); + + return success; +} + +static bool saveExtractedPartitionFsSection(PartitionFileSystemContext *pfs_ctx, bool use_layeredfs_dir) +{ + u64 data_size = 0; + + PfsThreadData pfs_thread_data = {0}; + SharedThreadData *shared_thread_data = &(pfs_thread_data.shared_thread_data); + + bool success = false; + + if (!pfsGetTotalDataSize(pfs_ctx, &data_size)) + { + consolePrint("failed to calculate extracted partitionfs section size!\n"); + goto end; + } + + if (!data_size) + { + consolePrint("partitionfs section is empty!\n"); + goto end; + } + + pfs_thread_data.pfs_ctx = pfs_ctx; + pfs_thread_data.use_layeredfs_dir = use_layeredfs_dir; + shared_thread_data->total_size = data_size; + + consolePrint("extracted partitionfs section size: 0x%lX\n", data_size); + consoleRefresh(); + + success = spanDumpThreads(extractedPartitionFsReadThreadFunc, genericWriteThreadFunc, &pfs_thread_data); + +end: + return success; +} + +static bool saveRawRomFsSection(RomFileSystemContext *romfs_ctx, bool use_layeredfs_dir) +{ + u64 free_space = 0; + + RomFsThreadData romfs_thread_data = {0}; + SharedThreadData *shared_thread_data = &(romfs_thread_data.shared_thread_data); + + NcaFsSectionContext *nca_fs_ctx = romfs_ctx->default_storage_ctx->nca_fs_ctx; + NcaContext *nca_ctx = nca_fs_ctx->nca_ctx; + + u64 title_id = nca_ctx->title_id; + u8 title_type = nca_ctx->title_type; + + char subdir[0x20] = {0}, *filename = NULL; + u32 dev_idx = g_storageMenuElementOption.selected; + + bool success = false; + + romfs_thread_data.romfs_ctx = romfs_ctx; + romfs_thread_data.use_layeredfs_dir = use_layeredfs_dir; + shared_thread_data->total_size = romfs_ctx->size; + + consolePrint("raw romfs section size: 0x%lX\n", romfs_ctx->size); + + if (use_layeredfs_dir) + { + /* Only use base title IDs if we're dealing with patches. */ + title_id = (title_type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(title_id) : \ + (title_type == NcmContentMetaType_DataPatch ? titleGetAddOnContentIdByDataPatchId(title_id) : title_id)); + + filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "romfs.bin"); + } else { + snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Raw", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); + snprintf(path, MAX_ELEMENTS(path), "/%s #%u/%u.bin", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx); + + TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); + filename = generateOutputTitleFileName(title_info, subdir, path); + } + + if (!filename) goto end; + + if (dev_idx == 1) + { + if (!usbSendFileProperties(shared_thread_data->total_size, filename)) + { + consolePrint("failed to send file properties for \"%s\"!\n", filename); + goto end; + } + } else { + if (!utilsGetFileSystemStatsByPath(filename, NULL, &free_space)) + { + consolePrint("failed to retrieve free space from selected device\n"); + goto end; + } + + if (shared_thread_data->total_size >= free_space) + { + consolePrint("dump size exceeds free space\n"); + goto end; + } + + utilsCreateDirectoryTree(filename, false); + + if (dev_idx == 0) + { + if (shared_thread_data->total_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(filename)) + { + consolePrint("failed to create concatenation file for \"%s\"!\n", filename); + goto end; + } + } else { + if (g_umsDevices[dev_idx - 2].fs_type < UsbHsFsDeviceFileSystemType_exFAT && shared_thread_data->total_size > FAT32_FILESIZE_LIMIT) + { + consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n"); + goto end; + } + } + + shared_thread_data->fp = fopen(filename, "wb"); + if (!shared_thread_data->fp) + { + consolePrint("failed to open \"%s\" for writing!\n", filename); + goto end; + } + + setvbuf(shared_thread_data->fp, NULL, _IONBF, 0); + ftruncate(fileno(shared_thread_data->fp), (off_t)shared_thread_data->total_size); + } + + consoleRefresh(); + + success = spanDumpThreads(rawRomFsReadThreadFunc, genericWriteThreadFunc, &romfs_thread_data); + + if (success) + { + consolePrint("successfully saved raw romfs section as \"%s\"\n", filename); + consoleRefresh(); + } + +end: + if (shared_thread_data->fp) + { + fclose(shared_thread_data->fp); + shared_thread_data->fp = NULL; + + if (!success && dev_idx != 1) + { + if (dev_idx == 0) + { + utilsRemoveConcatenationFile(filename); + utilsCommitSdCardFileSystemChanges(); + } else { + remove(filename); + } + } + } + + if (filename) free(filename); + + return success; +} + +static bool saveExtractedRomFsSection(RomFileSystemContext *romfs_ctx, bool use_layeredfs_dir) +{ + u64 data_size = 0; + + RomFsThreadData romfs_thread_data = {0}; + SharedThreadData *shared_thread_data = &(romfs_thread_data.shared_thread_data); + + bool success = false; + + if (!romfsGetTotalDataSize(romfs_ctx, false, &data_size)) + { + consolePrint("failed to calculate extracted romfs section size!\n"); + goto end; + } + + if (!data_size) + { + consolePrint("romfs section is empty!\n"); + goto end; + } + + romfs_thread_data.romfs_ctx = romfs_ctx; + romfs_thread_data.use_layeredfs_dir = use_layeredfs_dir; + shared_thread_data->total_size = data_size; + + consolePrint("extracted romfs section size: 0x%lX\n", data_size); + consoleRefresh(); + + success = spanDumpThreads(extractedRomFsReadThreadFunc, genericWriteThreadFunc, &romfs_thread_data); + +end: + return success; +} + +static void xciReadThreadFunc(void *arg) +{ + void *buf1 = NULL, *buf2 = NULL; + XciThreadData *xci_thread_data = (XciThreadData*)arg; + SharedThreadData *shared_thread_data = &(xci_thread_data->shared_thread_data); + + buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + + if (!shared_thread_data->total_size || !buf1 || !buf2) + { + shared_thread_data->read_error = true; + goto end; + } + + shared_thread_data->data = NULL; + shared_thread_data->data_size = 0; + + bool prepend_key_area = (bool)getGameCardPrependKeyAreaOption(); + bool keep_certificate = (bool)getGameCardKeepCertificateOption(); + bool calculate_checksum = (bool)getGameCardCalculateChecksumOption(); + + for(u64 offset = 0, blksize = BLOCK_SIZE; offset < shared_thread_data->total_size; offset += blksize) + { + if (blksize > (shared_thread_data->total_size - offset)) blksize = (shared_thread_data->total_size - offset); + + /* Check if the transfer has been cancelled by the user */ + if (shared_thread_data->transfer_cancelled) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + /* Read current data chunk */ + shared_thread_data->read_error = !gamecardReadStorage(buf1, blksize, offset); + if (shared_thread_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + /* Remove certificate */ + if (!keep_certificate && offset == 0) memset((u8*)buf1 + GAMECARD_CERTIFICATE_OFFSET, 0xFF, sizeof(FsGameCardCertificate)); + + /* Update checksum */ + if (calculate_checksum) + { + xci_thread_data->xci_crc = crc32CalculateWithSeed(xci_thread_data->xci_crc, buf1, blksize); + if (prepend_key_area) xci_thread_data->full_xci_crc = crc32CalculateWithSeed(xci_thread_data->full_xci_crc, buf1, blksize); + } + + /* Wait until the previous data chunk has been written */ + mutexLock(&g_fileMutex); + + if (shared_thread_data->data_size && !shared_thread_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex); + + if (shared_thread_data->write_error) + { + mutexUnlock(&g_fileMutex); + break; + } + + /* Update shared object. */ + shared_thread_data->data = buf1; + shared_thread_data->data_size = blksize; + + /* Swap buffers. */ + buf1 = buf2; + buf2 = shared_thread_data->data; + + /* Wake up the write thread to continue writing data. */ + mutexUnlock(&g_fileMutex); + condvarWakeAll(&g_writeCondvar); + } + +end: + if (buf2) free(buf2); + if (buf1) free(buf1); + + threadExit(); +} + +static void rawHfsReadThreadFunc(void *arg) +{ + void *buf1 = NULL, *buf2 = NULL; + HfsThreadData *hfs_thread_data = (HfsThreadData*)arg; + SharedThreadData *shared_thread_data = &(hfs_thread_data->shared_thread_data); + HashFileSystemContext *hfs_ctx = hfs_thread_data->hfs_ctx; + + buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + + if (!shared_thread_data->total_size || !hfs_ctx || !buf1 || !buf2) + { + shared_thread_data->read_error = true; + goto end; + } + + shared_thread_data->data = NULL; + shared_thread_data->data_size = 0; + + for(u64 offset = 0, blksize = BLOCK_SIZE; offset < shared_thread_data->total_size; offset += blksize) + { + if (blksize > (shared_thread_data->total_size - offset)) blksize = (shared_thread_data->total_size - offset); + + /* Check if the transfer has been cancelled by the user */ + if (shared_thread_data->transfer_cancelled) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + /* Read current data chunk */ + shared_thread_data->read_error = !hfsReadPartitionData(hfs_ctx, buf1, blksize, offset); + if (shared_thread_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + /* Wait until the previous data chunk has been written */ + mutexLock(&g_fileMutex); + + if (shared_thread_data->data_size && !shared_thread_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex); + + if (shared_thread_data->write_error) + { + mutexUnlock(&g_fileMutex); + break; + } + + /* Update shared object. */ + shared_thread_data->data = buf1; + shared_thread_data->data_size = blksize; + + /* Swap buffers. */ + buf1 = buf2; + buf2 = shared_thread_data->data; + + /* Wake up the write thread to continue writing data. */ + mutexUnlock(&g_fileMutex); + condvarWakeAll(&g_writeCondvar); + } + +end: + if (buf2) free(buf2); + if (buf1) free(buf1); + + threadExit(); +} + +static void extractedHfsReadThreadFunc(void *arg) +{ + void *buf1 = NULL, *buf2 = NULL; + HfsThreadData *hfs_thread_data = (HfsThreadData*)arg; + SharedThreadData *shared_thread_data = &(hfs_thread_data->shared_thread_data); + + HashFileSystemContext *hfs_ctx = hfs_thread_data->hfs_ctx; + u32 hfs_entry_count = hfsGetEntryCount(hfs_ctx); + + char hfs_path[FS_MAX_PATH] = {0}, *filename = NULL; + size_t filename_len = 0; + + HashFileSystemEntry *hfs_entry = NULL; + char *hfs_entry_name = NULL; + + u64 free_space = 0; + u32 dev_idx = g_storageMenuElementOption.selected; + + buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + + snprintf(hfs_path, MAX_ELEMENTS(hfs_path), "/%s", hfs_ctx->name); + filename = generateOutputGameCardFileName("HFS/Extracted", hfs_path, true); + filename_len = (filename ? strlen(filename) : 0); + + if (!shared_thread_data->total_size || !hfs_entry_count || !buf1 || !buf2 || !filename) + { + shared_thread_data->read_error = true; + goto end; + } + + if (dev_idx != 1) + { + if (!utilsGetFileSystemStatsByPath(filename, NULL, &free_space)) + { + consolePrint("failed to retrieve free space from selected device\n"); + shared_thread_data->read_error = true; + } + + if (!shared_thread_data->read_error && shared_thread_data->total_size >= free_space) + { + consolePrint("dump size exceeds free space\n"); + shared_thread_data->read_error = true; + } + } else { + if (!usbStartExtractedFsDump(shared_thread_data->total_size, filename)) + { + consolePrint("failed to send extracted fs info to host\n"); + shared_thread_data->read_error = true; + } + } + + if (shared_thread_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + goto end; + } + + /* Loop through all file entries. */ + for(u32 i = 0; i < hfs_entry_count; i++) + { + /* Check if the transfer has been cancelled by the user. */ + if (shared_thread_data->transfer_cancelled) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + if (dev_idx != 1) + { + /* Wait until the previous data chunk has been written */ + mutexLock(&g_fileMutex); + if (shared_thread_data->data_size && !shared_thread_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex); + mutexUnlock(&g_fileMutex); + + if (shared_thread_data->write_error) break; + + /* Close file. */ + if (shared_thread_data->fp) + { + fclose(shared_thread_data->fp); + shared_thread_data->fp = NULL; + if (dev_idx == 0) utilsCommitSdCardFileSystemChanges(); + } + } + + /* Retrieve Hash FS file entry information. */ + shared_thread_data->read_error = ((hfs_entry = hfsGetEntryByIndex(hfs_ctx, i)) == NULL || (hfs_entry_name = hfsGetEntryName(hfs_ctx, hfs_entry)) == NULL); + if (shared_thread_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + /* Generate output path. */ + snprintf(hfs_path, MAX_ELEMENTS(hfs_path), "%s/%s", filename, hfs_entry_name); + utilsReplaceIllegalCharacters(hfs_path + filename_len + 1, dev_idx == 0); + + if (dev_idx == 1) + { + /* Wait until the previous data chunk has been written */ + mutexLock(&g_fileMutex); + if (shared_thread_data->data_size && !shared_thread_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex); + mutexUnlock(&g_fileMutex); + + if (shared_thread_data->write_error) break; + + /* Send current file properties */ + shared_thread_data->read_error = !usbSendFileProperties(hfs_entry->size, hfs_path); + } else { + /* Create directory tree. */ + utilsCreateDirectoryTree(hfs_path, false); + + if (dev_idx == 0) + { + /* Create ConcatenationFile if we're dealing with a big file + SD card as the output storage. */ + if (hfs_entry->size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(hfs_path)) + { + consolePrint("failed to create concatenation file for \"%s\"!\n", hfs_path); + shared_thread_data->read_error = true; + } + } else { + /* Don't handle file chunks on FAT12/FAT16/FAT32 formatted UMS devices. */ + if (g_umsDevices[dev_idx - 2].fs_type < UsbHsFsDeviceFileSystemType_exFAT && hfs_entry->size > FAT32_FILESIZE_LIMIT) + { + consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n"); + shared_thread_data->read_error = true; + } + } + + if (!shared_thread_data->read_error) + { + /* Open output file. */ + shared_thread_data->read_error = ((shared_thread_data->fp = fopen(hfs_path, "wb")) == NULL); + if (!shared_thread_data->read_error) + { + /* Set file size. */ + setvbuf(shared_thread_data->fp, NULL, _IONBF, 0); + ftruncate(fileno(shared_thread_data->fp), (off_t)hfs_entry->size); + } else { + consolePrint("failed to open \"%s\" for writing!\n", hfs_path); + } + } + } + + if (shared_thread_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + for(u64 offset = 0, blksize = BLOCK_SIZE; offset < hfs_entry->size; offset += blksize) + { + if (blksize > (hfs_entry->size - offset)) blksize = (hfs_entry->size - offset); + + /* Check if the transfer has been cancelled by the user. */ + if (shared_thread_data->transfer_cancelled) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + /* Read current file data chunk. */ + shared_thread_data->read_error = !hfsReadEntryData(hfs_ctx, hfs_entry, buf1, blksize, offset); + if (shared_thread_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + /* Wait until the previous file data chunk has been written. */ + mutexLock(&g_fileMutex); + + if (shared_thread_data->data_size && !shared_thread_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex); + + if (shared_thread_data->write_error) + { + mutexUnlock(&g_fileMutex); + break; + } + + /* Update shared object. */ + shared_thread_data->data = buf1; + shared_thread_data->data_size = blksize; + + /* Swap buffers. */ + buf1 = buf2; + buf2 = shared_thread_data->data; + + /* Wake up the write thread to continue writing data. */ + mutexUnlock(&g_fileMutex); + condvarWakeAll(&g_writeCondvar); } - setvbuf(shared_thread_data->fp, NULL, _IONBF, 0); - ftruncate(fileno(shared_thread_data->fp), (off_t)shared_thread_data->total_size); + if (shared_thread_data->read_error || shared_thread_data->write_error || shared_thread_data->transfer_cancelled) break; } - consoleRefresh(); + if (!shared_thread_data->read_error && !shared_thread_data->write_error && !shared_thread_data->transfer_cancelled) + { + /* Wait until the previous file data chunk has been written. */ + mutexLock(&g_fileMutex); + if (shared_thread_data->data_size) condvarWait(&g_readCondvar, &g_fileMutex); + mutexUnlock(&g_fileMutex); - success = spanDumpThreads(rawRomFsReadThreadFunc, genericWriteThreadFunc, &romfs_thread_data); + if (dev_idx == 1) usbEndExtractedFsDump(); - if (success) - { - consolePrint("successfully saved raw romfs section as \"%s\"\n", filename); + consolePrint("successfully saved extracted hfs partition data to \"%s\"\n", filename); consoleRefresh(); } @@ -3644,67 +4730,32 @@ static bool saveRawRomFsSection(RomFileSystemContext *romfs_ctx, bool use_layere fclose(shared_thread_data->fp); shared_thread_data->fp = NULL; - if (!success && dev_idx != 1) + if ((shared_thread_data->read_error || shared_thread_data->write_error || shared_thread_data->transfer_cancelled) && dev_idx != 1) { - if (dev_idx == 0) - { - utilsRemoveConcatenationFile(filename); - utilsCommitSdCardFileSystemChanges(); - } else { - remove(filename); - } + utilsDeleteDirectoryRecursively(filename); + if (dev_idx == 0) utilsCommitSdCardFileSystemChanges(); } } if (filename) free(filename); - return success; -} - -static bool saveExtractedRomFsSection(RomFileSystemContext *romfs_ctx, bool use_layeredfs_dir) -{ - u64 data_size = 0; - - RomFsThreadData romfs_thread_data = {0}; - SharedThreadData *shared_thread_data = &(romfs_thread_data.shared_thread_data); - - bool success = false; - - if (!romfsGetTotalDataSize(romfs_ctx, false, &data_size)) - { - consolePrint("failed to calculate extracted romfs section size!\n"); - goto end; - } - - if (!data_size) - { - consolePrint("romfs section is empty!\n"); - goto end; - } - - romfs_thread_data.romfs_ctx = romfs_ctx; - romfs_thread_data.use_layeredfs_dir = use_layeredfs_dir; - shared_thread_data->total_size = data_size; - - consolePrint("extracted romfs section size: 0x%lX\n", data_size); - consoleRefresh(); - - success = spanDumpThreads(extractedRomFsReadThreadFunc, genericWriteThreadFunc, &romfs_thread_data); + if (buf2) free(buf2); + if (buf1) free(buf1); -end: - return success; + threadExit(); } -static void xciReadThreadFunc(void *arg) +static void ncaReadThreadFunc(void *arg) { void *buf1 = NULL, *buf2 = NULL; - XciThreadData *xci_thread_data = (XciThreadData*)arg; - SharedThreadData *shared_thread_data = &(xci_thread_data->shared_thread_data); + NcaThreadData *nca_thread_data = (NcaThreadData*)arg; + SharedThreadData *shared_thread_data = &(nca_thread_data->shared_thread_data); + NcaContext *nca_ctx = nca_thread_data->nca_ctx; buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); - if (!shared_thread_data->total_size || !buf1 || !buf2) + if (!shared_thread_data->total_size || !nca_ctx || !buf1 || !buf2) { shared_thread_data->read_error = true; goto end; @@ -3713,10 +4764,6 @@ static void xciReadThreadFunc(void *arg) shared_thread_data->data = NULL; shared_thread_data->data_size = 0; - bool prepend_key_area = (bool)getGameCardPrependKeyAreaOption(); - bool keep_certificate = (bool)getGameCardKeepCertificateOption(); - bool calculate_checksum = (bool)getGameCardCalculateChecksumOption(); - for(u64 offset = 0, blksize = BLOCK_SIZE; offset < shared_thread_data->total_size; offset += blksize) { if (blksize > (shared_thread_data->total_size - offset)) blksize = (shared_thread_data->total_size - offset); @@ -3729,23 +4776,13 @@ static void xciReadThreadFunc(void *arg) } /* Read current data chunk */ - shared_thread_data->read_error = !gamecardReadStorage(buf1, blksize, offset); + shared_thread_data->read_error = !ncaReadContentFile(nca_ctx, buf1, blksize, offset); if (shared_thread_data->read_error) { condvarWakeAll(&g_writeCondvar); break; } - /* Remove certificate */ - if (!keep_certificate && offset == 0) memset((u8*)buf1 + GAMECARD_CERTIFICATE_OFFSET, 0xFF, sizeof(FsGameCardCertificate)); - - /* Update checksum */ - if (calculate_checksum) - { - xci_thread_data->xci_crc = crc32CalculateWithSeed(xci_thread_data->xci_crc, buf1, blksize); - if (prepend_key_area) xci_thread_data->full_xci_crc = crc32CalculateWithSeed(xci_thread_data->full_xci_crc, buf1, blksize); - } - /* Wait until the previous data chunk has been written */ mutexLock(&g_fileMutex); @@ -3777,17 +4814,17 @@ static void xciReadThreadFunc(void *arg) threadExit(); } -static void rawHfsReadThreadFunc(void *arg) +static void rawPartitionFsReadThreadFunc(void *arg) { void *buf1 = NULL, *buf2 = NULL; - HfsThreadData *hfs_thread_data = (HfsThreadData*)arg; - SharedThreadData *shared_thread_data = &(hfs_thread_data->shared_thread_data); - HashFileSystemContext *hfs_ctx = hfs_thread_data->hfs_ctx; + PfsThreadData *pfs_thread_data = (PfsThreadData*)arg; + SharedThreadData *shared_thread_data = &(pfs_thread_data->shared_thread_data); + PartitionFileSystemContext *pfs_ctx = pfs_thread_data->pfs_ctx; buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); - if (!shared_thread_data->total_size || !hfs_ctx || !buf1 || !buf2) + if (!shared_thread_data->total_size || !pfs_ctx || !buf1 || !buf2) { shared_thread_data->read_error = true; goto end; @@ -3808,7 +4845,7 @@ static void rawHfsReadThreadFunc(void *arg) } /* Read current data chunk */ - shared_thread_data->read_error = !hfsReadPartitionData(hfs_ctx, buf1, blksize, offset); + shared_thread_data->read_error = !pfsReadPartitionData(pfs_ctx, buf1, blksize, offset); if (shared_thread_data->read_error) { condvarWakeAll(&g_writeCondvar); @@ -3846,20 +4883,26 @@ static void rawHfsReadThreadFunc(void *arg) threadExit(); } -static void extractedHfsReadThreadFunc(void *arg) +static void extractedPartitionFsReadThreadFunc(void *arg) { void *buf1 = NULL, *buf2 = NULL; - HfsThreadData *hfs_thread_data = (HfsThreadData*)arg; - SharedThreadData *shared_thread_data = &(hfs_thread_data->shared_thread_data); + PfsThreadData *pfs_thread_data = (PfsThreadData*)arg; + SharedThreadData *shared_thread_data = &(pfs_thread_data->shared_thread_data); - HashFileSystemContext *hfs_ctx = hfs_thread_data->hfs_ctx; - u32 hfs_entry_count = hfsGetEntryCount(hfs_ctx); + PartitionFileSystemContext *pfs_ctx = pfs_thread_data->pfs_ctx; + u32 pfs_entry_count = pfsGetEntryCount(pfs_ctx); - char hfs_path[FS_MAX_PATH] = {0}, *filename = NULL; + char pfs_path[FS_MAX_PATH] = {0}, subdir[0x20] = {0}, *filename = NULL; size_t filename_len = 0; - HashFileSystemEntry *hfs_entry = NULL; - char *hfs_entry_name = NULL; + PartitionFileSystemEntry *pfs_entry = NULL; + char *pfs_entry_name = NULL; + + NcaFsSectionContext *nca_fs_ctx = pfs_ctx->nca_fs_ctx; + NcaContext *nca_ctx = nca_fs_ctx->nca_ctx; + + u64 title_id = nca_ctx->title_id; + u8 title_type = nca_ctx->title_type; u64 free_space = 0; u32 dev_idx = g_storageMenuElementOption.selected; @@ -3867,11 +4910,24 @@ static void extractedHfsReadThreadFunc(void *arg) buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); - snprintf(hfs_path, MAX_ELEMENTS(hfs_path), "/%s", hfs_ctx->name); - filename = generateOutputGameCardFileName("HFS/Extracted", hfs_path, true); + if (pfs_thread_data->use_layeredfs_dir) + { + /* Only use base title IDs if we're dealing with patches. */ + title_id = (title_type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(title_id) : \ + (title_type == NcmContentMetaType_DataPatch ? titleGetAddOnContentIdByDataPatchId(title_id) : title_id)); + + filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "exefs"); + } else { + snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Extracted", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); + snprintf(pfs_path, MAX_ELEMENTS(pfs_path), "/%s #%u/%u", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx); + + TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); + filename = generateOutputTitleFileName(title_info, subdir, pfs_path); + } + filename_len = (filename ? strlen(filename) : 0); - if (!shared_thread_data->total_size || !hfs_entry_count || !buf1 || !buf2 || !filename) + if (!shared_thread_data->total_size || !pfs_entry_count || !buf1 || !buf2 || !filename) { shared_thread_data->read_error = true; goto end; @@ -3905,7 +4961,7 @@ static void extractedHfsReadThreadFunc(void *arg) } /* Loop through all file entries. */ - for(u32 i = 0; i < hfs_entry_count; i++) + for(u32 i = 0; i < pfs_entry_count; i++) { /* Check if the transfer has been cancelled by the user. */ if (shared_thread_data->transfer_cancelled) @@ -3928,12 +4984,12 @@ static void extractedHfsReadThreadFunc(void *arg) { fclose(shared_thread_data->fp); shared_thread_data->fp = NULL; - utilsCommitSdCardFileSystemChanges(); + if (dev_idx == 0) utilsCommitSdCardFileSystemChanges(); } } - /* Retrieve Hash FS file entry information. */ - shared_thread_data->read_error = ((hfs_entry = hfsGetEntryByIndex(hfs_ctx, i)) == NULL || (hfs_entry_name = hfsGetEntryName(hfs_ctx, hfs_entry)) == NULL); + /* Retrieve Partition FS file entry information. */ + shared_thread_data->read_error = ((pfs_entry = pfsGetEntryByIndex(pfs_ctx, i)) == NULL || (pfs_entry_name = pfsGetEntryName(pfs_ctx, pfs_entry)) == NULL); if (shared_thread_data->read_error) { condvarWakeAll(&g_writeCondvar); @@ -3941,8 +4997,8 @@ static void extractedHfsReadThreadFunc(void *arg) } /* Generate output path. */ - snprintf(hfs_path, MAX_ELEMENTS(hfs_path), "%s/%s", filename, hfs_entry_name); - utilsReplaceIllegalCharacters(hfs_path + filename_len + 1, dev_idx == 0); + snprintf(pfs_path, MAX_ELEMENTS(pfs_path), "%s/%s", filename, pfs_entry_name); + utilsReplaceIllegalCharacters(pfs_path + filename_len + 1, dev_idx == 0); if (dev_idx == 1) { @@ -3954,22 +5010,22 @@ static void extractedHfsReadThreadFunc(void *arg) if (shared_thread_data->write_error) break; /* Send current file properties */ - shared_thread_data->read_error = !usbSendFileProperties(hfs_entry->size, hfs_path); + shared_thread_data->read_error = !usbSendFileProperties(pfs_entry->size, pfs_path); } else { /* Create directory tree. */ - utilsCreateDirectoryTree(hfs_path, false); + utilsCreateDirectoryTree(pfs_path, false); if (dev_idx == 0) { /* Create ConcatenationFile if we're dealing with a big file + SD card as the output storage. */ - if (hfs_entry->size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(hfs_path)) + if (pfs_entry->size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(pfs_path)) { - consolePrint("failed to create concatenation file for \"%s\"!\n", hfs_path); + consolePrint("failed to create concatenation file for \"%s\"!\n", pfs_path); shared_thread_data->read_error = true; } } else { /* Don't handle file chunks on FAT12/FAT16/FAT32 formatted UMS devices. */ - if (g_umsDevices[dev_idx - 2].fs_type < UsbHsFsDeviceFileSystemType_exFAT && hfs_entry->size > FAT32_FILESIZE_LIMIT) + if (g_umsDevices[dev_idx - 2].fs_type < UsbHsFsDeviceFileSystemType_exFAT && pfs_entry->size > FAT32_FILESIZE_LIMIT) { consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n"); shared_thread_data->read_error = true; @@ -3979,14 +5035,14 @@ static void extractedHfsReadThreadFunc(void *arg) if (!shared_thread_data->read_error) { /* Open output file. */ - shared_thread_data->read_error = ((shared_thread_data->fp = fopen(hfs_path, "wb")) == NULL); + shared_thread_data->read_error = ((shared_thread_data->fp = fopen(pfs_path, "wb")) == NULL); if (!shared_thread_data->read_error) { /* Set file size. */ setvbuf(shared_thread_data->fp, NULL, _IONBF, 0); - ftruncate(fileno(shared_thread_data->fp), (off_t)hfs_entry->size); + ftruncate(fileno(shared_thread_data->fp), (off_t)pfs_entry->size); } else { - consolePrint("failed to open \"%s\" for writing!\n", hfs_path); + consolePrint("failed to open \"%s\" for writing!\n", pfs_path); } } } @@ -3997,9 +5053,9 @@ static void extractedHfsReadThreadFunc(void *arg) break; } - for(u64 offset = 0, blksize = BLOCK_SIZE; offset < hfs_entry->size; offset += blksize) + for(u64 offset = 0, blksize = BLOCK_SIZE; offset < pfs_entry->size; offset += blksize) { - if (blksize > (hfs_entry->size - offset)) blksize = (hfs_entry->size - offset); + if (blksize > (pfs_entry->size - offset)) blksize = (pfs_entry->size - offset); /* Check if the transfer has been cancelled by the user. */ if (shared_thread_data->transfer_cancelled) @@ -4009,7 +5065,7 @@ static void extractedHfsReadThreadFunc(void *arg) } /* Read current file data chunk. */ - shared_thread_data->read_error = !hfsReadEntryData(hfs_ctx, hfs_entry, buf1, blksize, offset); + shared_thread_data->read_error = !pfsReadEntryData(pfs_ctx, pfs_entry, buf1, blksize, offset); if (shared_thread_data->read_error) { condvarWakeAll(&g_writeCondvar); @@ -4031,132 +5087,63 @@ static void extractedHfsReadThreadFunc(void *arg) shared_thread_data->data = buf1; shared_thread_data->data_size = blksize; - /* Swap buffers. */ - buf1 = buf2; - buf2 = shared_thread_data->data; - - /* Wake up the write thread to continue writing data. */ - mutexUnlock(&g_fileMutex); - condvarWakeAll(&g_writeCondvar); - } - - if (shared_thread_data->read_error || shared_thread_data->write_error || shared_thread_data->transfer_cancelled) break; - } - - if (!shared_thread_data->read_error && !shared_thread_data->write_error && !shared_thread_data->transfer_cancelled) - { - /* Wait until the previous file data chunk has been written. */ - mutexLock(&g_fileMutex); - if (shared_thread_data->data_size) condvarWait(&g_readCondvar, &g_fileMutex); - mutexUnlock(&g_fileMutex); - - if (dev_idx == 1) usbEndExtractedFsDump(); - - consolePrint("successfully saved extracted hfs partition data to \"%s\"\n", filename); - consoleRefresh(); - } - -end: - if (shared_thread_data->fp) - { - fclose(shared_thread_data->fp); - shared_thread_data->fp = NULL; - - if ((shared_thread_data->read_error || shared_thread_data->write_error || shared_thread_data->transfer_cancelled) && dev_idx != 1) - { - utilsDeleteDirectoryRecursively(filename); - if (dev_idx == 0) utilsCommitSdCardFileSystemChanges(); - } - } - - if (filename) free(filename); - - if (buf2) free(buf2); - if (buf1) free(buf1); - - threadExit(); -} - -static void ncaReadThreadFunc(void *arg) -{ - void *buf1 = NULL, *buf2 = NULL; - NcaThreadData *nca_thread_data = (NcaThreadData*)arg; - SharedThreadData *shared_thread_data = &(nca_thread_data->shared_thread_data); - NcaContext *nca_ctx = nca_thread_data->nca_ctx; - - buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); - buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); - - if (!shared_thread_data->total_size || !nca_ctx || !buf1 || !buf2) - { - shared_thread_data->read_error = true; - goto end; - } - - shared_thread_data->data = NULL; - shared_thread_data->data_size = 0; - - for(u64 offset = 0, blksize = BLOCK_SIZE; offset < shared_thread_data->total_size; offset += blksize) - { - if (blksize > (shared_thread_data->total_size - offset)) blksize = (shared_thread_data->total_size - offset); + /* Swap buffers. */ + buf1 = buf2; + buf2 = shared_thread_data->data; - /* Check if the transfer has been cancelled by the user */ - if (shared_thread_data->transfer_cancelled) - { + /* Wake up the write thread to continue writing data. */ + mutexUnlock(&g_fileMutex); condvarWakeAll(&g_writeCondvar); - break; } - /* Read current data chunk */ - shared_thread_data->read_error = !ncaReadContentFile(nca_ctx, buf1, blksize, offset); - if (shared_thread_data->read_error) - { - condvarWakeAll(&g_writeCondvar); - break; - } + if (shared_thread_data->read_error || shared_thread_data->write_error || shared_thread_data->transfer_cancelled) break; + } - /* Wait until the previous data chunk has been written */ + if (!shared_thread_data->read_error && !shared_thread_data->write_error && !shared_thread_data->transfer_cancelled) + { + /* Wait until the previous file data chunk has been written. */ mutexLock(&g_fileMutex); + if (shared_thread_data->data_size) condvarWait(&g_readCondvar, &g_fileMutex); + mutexUnlock(&g_fileMutex); - if (shared_thread_data->data_size && !shared_thread_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex); - - if (shared_thread_data->write_error) - { - mutexUnlock(&g_fileMutex); - break; - } + if (dev_idx == 1) usbEndExtractedFsDump(); - /* Update shared object. */ - shared_thread_data->data = buf1; - shared_thread_data->data_size = blksize; + consolePrint("successfully saved extracted partitionfs section data to \"%s\"\n", filename); + consoleRefresh(); + } - /* Swap buffers. */ - buf1 = buf2; - buf2 = shared_thread_data->data; +end: + if (shared_thread_data->fp) + { + fclose(shared_thread_data->fp); + shared_thread_data->fp = NULL; - /* Wake up the write thread to continue writing data. */ - mutexUnlock(&g_fileMutex); - condvarWakeAll(&g_writeCondvar); + if ((shared_thread_data->read_error || shared_thread_data->write_error || shared_thread_data->transfer_cancelled) && dev_idx != 1) + { + utilsDeleteDirectoryRecursively(filename); + if (dev_idx == 0) utilsCommitSdCardFileSystemChanges(); + } } -end: + if (filename) free(filename); + if (buf2) free(buf2); if (buf1) free(buf1); threadExit(); } -static void rawPartitionFsReadThreadFunc(void *arg) +static void rawRomFsReadThreadFunc(void *arg) { void *buf1 = NULL, *buf2 = NULL; - PfsThreadData *pfs_thread_data = (PfsThreadData*)arg; - SharedThreadData *shared_thread_data = &(pfs_thread_data->shared_thread_data); - PartitionFileSystemContext *pfs_ctx = pfs_thread_data->pfs_ctx; + RomFsThreadData *romfs_thread_data = (RomFsThreadData*)arg; + SharedThreadData *shared_thread_data = &(romfs_thread_data->shared_thread_data); + RomFileSystemContext *romfs_ctx = romfs_thread_data->romfs_ctx; buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); - if (!shared_thread_data->total_size || !pfs_ctx || !buf1 || !buf2) + if (!shared_thread_data->total_size || !romfs_ctx || !buf1 || !buf2) { shared_thread_data->read_error = true; goto end; @@ -4177,7 +5164,7 @@ static void rawPartitionFsReadThreadFunc(void *arg) } /* Read current data chunk */ - shared_thread_data->read_error = !pfsReadPartitionData(pfs_ctx, buf1, blksize, offset); + shared_thread_data->read_error = !romfsReadFileSystemData(romfs_ctx, buf1, blksize, offset); if (shared_thread_data->read_error) { condvarWakeAll(&g_writeCondvar); @@ -4215,22 +5202,20 @@ static void rawPartitionFsReadThreadFunc(void *arg) threadExit(); } -static void extractedPartitionFsReadThreadFunc(void *arg) +static void extractedRomFsReadThreadFunc(void *arg) { void *buf1 = NULL, *buf2 = NULL; - PfsThreadData *pfs_thread_data = (PfsThreadData*)arg; - SharedThreadData *shared_thread_data = &(pfs_thread_data->shared_thread_data); + RomFsThreadData *romfs_thread_data = (RomFsThreadData*)arg; + SharedThreadData *shared_thread_data = &(romfs_thread_data->shared_thread_data); - PartitionFileSystemContext *pfs_ctx = pfs_thread_data->pfs_ctx; - u32 pfs_entry_count = pfsGetEntryCount(pfs_ctx); + RomFileSystemContext *romfs_ctx = romfs_thread_data->romfs_ctx; + RomFileSystemFileEntry *romfs_file_entry = NULL; + u64 cur_entry_offset = 0; - char pfs_path[FS_MAX_PATH] = {0}, subdir[0x20] = {0}, *filename = NULL; + char romfs_path[FS_MAX_PATH] = {0}, subdir[0x20] = {0}, *filename = NULL; size_t filename_len = 0; - PartitionFileSystemEntry *pfs_entry = NULL; - char *pfs_entry_name = NULL; - - NcaFsSectionContext *nca_fs_ctx = pfs_ctx->nca_fs_ctx; + NcaFsSectionContext *nca_fs_ctx = romfs_ctx->default_storage_ctx->nca_fs_ctx; NcaContext *nca_ctx = nca_fs_ctx->nca_ctx; u64 title_id = nca_ctx->title_id; @@ -4238,33 +5223,36 @@ static void extractedPartitionFsReadThreadFunc(void *arg) u64 free_space = 0; u32 dev_idx = g_storageMenuElementOption.selected; + u8 romfs_illegal_char_replace_type = (dev_idx != 0 ? RomFileSystemPathIllegalCharReplaceType_IllegalFsChars : RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly); buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); - if (pfs_thread_data->use_layeredfs_dir) + if (romfs_thread_data->use_layeredfs_dir) { /* Only use base title IDs if we're dealing with patches. */ title_id = (title_type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(title_id) : \ (title_type == NcmContentMetaType_DataPatch ? titleGetAddOnContentIdByDataPatchId(title_id) : title_id)); - filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "exefs"); + filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "romfs"); } else { snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Extracted", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); - snprintf(pfs_path, MAX_ELEMENTS(pfs_path), "/%s #%u/%u", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx); + snprintf(romfs_path, MAX_ELEMENTS(romfs_path), "/%s #%u/%u", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx); TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); - filename = generateOutputTitleFileName(title_info, subdir, pfs_path); + filename = generateOutputTitleFileName(title_info, subdir, romfs_path); } filename_len = (filename ? strlen(filename) : 0); - if (!shared_thread_data->total_size || !pfs_entry_count || !buf1 || !buf2 || !filename) + if (!shared_thread_data->total_size || !buf1 || !buf2 || !filename) { shared_thread_data->read_error = true; goto end; } + snprintf(romfs_path, MAX_ELEMENTS(romfs_path), "%s", filename); + if (dev_idx != 1) { if (!utilsGetFileSystemStatsByPath(filename, NULL, &free_space)) @@ -4293,7 +5281,7 @@ static void extractedPartitionFsReadThreadFunc(void *arg) } /* Loop through all file entries. */ - for(u32 i = 0; i < pfs_entry_count; i++) + while(shared_thread_data->data_written < shared_thread_data->total_size && cur_entry_offset < romfs_ctx->file_table_size) { /* Check if the transfer has been cancelled by the user. */ if (shared_thread_data->transfer_cancelled) @@ -4316,22 +5304,19 @@ static void extractedPartitionFsReadThreadFunc(void *arg) { fclose(shared_thread_data->fp); shared_thread_data->fp = NULL; - utilsCommitSdCardFileSystemChanges(); + if (dev_idx == 0) utilsCommitSdCardFileSystemChanges(); } } - /* Retrieve Partition FS file entry information. */ - shared_thread_data->read_error = ((pfs_entry = pfsGetEntryByIndex(pfs_ctx, i)) == NULL || (pfs_entry_name = pfsGetEntryName(pfs_ctx, pfs_entry)) == NULL); + /* Retrieve RomFS file entry information and generate output path. */ + shared_thread_data->read_error = (!(romfs_file_entry = romfsGetFileEntryByOffset(romfs_ctx, cur_entry_offset)) || \ + !romfsGeneratePathFromFileEntry(romfs_ctx, romfs_file_entry, romfs_path + filename_len, sizeof(romfs_path) - filename_len, romfs_illegal_char_replace_type)); if (shared_thread_data->read_error) { condvarWakeAll(&g_writeCondvar); break; } - /* Generate output path. */ - snprintf(pfs_path, MAX_ELEMENTS(pfs_path), "%s/%s", filename, pfs_entry_name); - utilsReplaceIllegalCharacters(pfs_path + filename_len + 1, dev_idx == 0); - if (dev_idx == 1) { /* Wait until the previous data chunk has been written */ @@ -4342,22 +5327,22 @@ static void extractedPartitionFsReadThreadFunc(void *arg) if (shared_thread_data->write_error) break; /* Send current file properties */ - shared_thread_data->read_error = !usbSendFileProperties(pfs_entry->size, pfs_path); + shared_thread_data->read_error = !usbSendFileProperties(romfs_file_entry->size, romfs_path); } else { /* Create directory tree. */ - utilsCreateDirectoryTree(pfs_path, false); + utilsCreateDirectoryTree(romfs_path, false); if (dev_idx == 0) { /* Create ConcatenationFile if we're dealing with a big file + SD card as the output storage. */ - if (pfs_entry->size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(pfs_path)) + if (romfs_file_entry->size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(romfs_path)) { - consolePrint("failed to create concatenation file for \"%s\"!\n", pfs_path); + consolePrint("failed to create concatenation file for \"%s\"!\n", romfs_path); shared_thread_data->read_error = true; } } else { /* Don't handle file chunks on FAT12/FAT16/FAT32 formatted UMS devices. */ - if (g_umsDevices[dev_idx - 2].fs_type < UsbHsFsDeviceFileSystemType_exFAT && pfs_entry->size > FAT32_FILESIZE_LIMIT) + if (g_umsDevices[dev_idx - 2].fs_type < UsbHsFsDeviceFileSystemType_exFAT && romfs_file_entry->size > FAT32_FILESIZE_LIMIT) { consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n"); shared_thread_data->read_error = true; @@ -4367,14 +5352,14 @@ static void extractedPartitionFsReadThreadFunc(void *arg) if (!shared_thread_data->read_error) { /* Open output file. */ - shared_thread_data->read_error = ((shared_thread_data->fp = fopen(pfs_path, "wb")) == NULL); + shared_thread_data->read_error = ((shared_thread_data->fp = fopen(romfs_path, "wb")) == NULL); if (!shared_thread_data->read_error) { /* Set file size. */ setvbuf(shared_thread_data->fp, NULL, _IONBF, 0); - ftruncate(fileno(shared_thread_data->fp), (off_t)pfs_entry->size); + ftruncate(fileno(shared_thread_data->fp), (off_t)romfs_file_entry->size); } else { - consolePrint("failed to open \"%s\" for writing!\n", pfs_path); + consolePrint("failed to open \"%s\" for writing!\n", romfs_path); } } } @@ -4385,9 +5370,9 @@ static void extractedPartitionFsReadThreadFunc(void *arg) break; } - for(u64 offset = 0, blksize = BLOCK_SIZE; offset < pfs_entry->size; offset += blksize) + for(u64 offset = 0, blksize = BLOCK_SIZE; offset < romfs_file_entry->size; offset += blksize) { - if (blksize > (pfs_entry->size - offset)) blksize = (pfs_entry->size - offset); + if (blksize > (romfs_file_entry->size - offset)) blksize = (romfs_file_entry->size - offset); /* Check if the transfer has been cancelled by the user. */ if (shared_thread_data->transfer_cancelled) @@ -4397,7 +5382,7 @@ static void extractedPartitionFsReadThreadFunc(void *arg) } /* Read current file data chunk. */ - shared_thread_data->read_error = !pfsReadEntryData(pfs_ctx, pfs_entry, buf1, blksize, offset); + shared_thread_data->read_error = !romfsReadFileEntryData(romfs_ctx, romfs_file_entry, buf1, blksize, offset); if (shared_thread_data->read_error) { condvarWakeAll(&g_writeCondvar); @@ -4429,6 +5414,9 @@ static void extractedPartitionFsReadThreadFunc(void *arg) } if (shared_thread_data->read_error || shared_thread_data->write_error || shared_thread_data->transfer_cancelled) break; + + /* Get the offset for the next file entry. */ + cur_entry_offset += ALIGN_UP(sizeof(RomFileSystemFileEntry) + romfs_file_entry->name_length, ROMFS_TABLE_ENTRY_ALIGNMENT); } if (!shared_thread_data->read_error && !shared_thread_data->write_error && !shared_thread_data->transfer_cancelled) @@ -4440,7 +5428,7 @@ static void extractedPartitionFsReadThreadFunc(void *arg) if (dev_idx == 1) usbEndExtractedFsDump(); - consolePrint("successfully saved extracted partitionfs section data to \"%s\"\n", filename); + consolePrint("successfully saved extracted romfs section data to \"%s\"\n", filename); consoleRefresh(); } @@ -4465,17 +5453,17 @@ static void extractedPartitionFsReadThreadFunc(void *arg) threadExit(); } -static void rawRomFsReadThreadFunc(void *arg) +static void fsBrowserFileReadThreadFunc(void *arg) { void *buf1 = NULL, *buf2 = NULL; - RomFsThreadData *romfs_thread_data = (RomFsThreadData*)arg; - SharedThreadData *shared_thread_data = &(romfs_thread_data->shared_thread_data); - RomFileSystemContext *romfs_ctx = romfs_thread_data->romfs_ctx; + FsBrowserFileThreadData *fs_browser_thread_data = (FsBrowserFileThreadData*)arg; + SharedThreadData *shared_thread_data = &(fs_browser_thread_data->shared_thread_data); + FILE *src = fs_browser_thread_data->src; buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); - if (!shared_thread_data->total_size || !romfs_ctx || !buf1 || !buf2) + if (!shared_thread_data->total_size || !src || !buf1 || !buf2) { shared_thread_data->read_error = true; goto end; @@ -4496,7 +5484,7 @@ static void rawRomFsReadThreadFunc(void *arg) } /* Read current data chunk */ - shared_thread_data->read_error = !romfsReadFileSystemData(romfs_ctx, buf1, blksize, offset); + shared_thread_data->read_error = (fread(buf1, 1, blksize, src) != blksize); if (shared_thread_data->read_error) { condvarWakeAll(&g_writeCondvar); @@ -4534,60 +5522,33 @@ static void rawRomFsReadThreadFunc(void *arg) threadExit(); } -static void extractedRomFsReadThreadFunc(void *arg) +static void fsBrowserHighlightedEntriesReadThreadFunc(void *arg) { void *buf1 = NULL, *buf2 = NULL; - RomFsThreadData *romfs_thread_data = (RomFsThreadData*)arg; - SharedThreadData *shared_thread_data = &(romfs_thread_data->shared_thread_data); - - RomFileSystemContext *romfs_ctx = romfs_thread_data->romfs_ctx; - RomFileSystemFileEntry *romfs_file_entry = NULL; - u64 cur_entry_offset = 0; + FsBrowserHighlightedEntriesThreadData *fs_browser_thread_data = (FsBrowserHighlightedEntriesThreadData*)arg; + SharedThreadData *shared_thread_data = &(fs_browser_thread_data->shared_thread_data); - char romfs_path[FS_MAX_PATH] = {0}, subdir[0x20] = {0}, *filename = NULL; - size_t filename_len = 0; - - NcaFsSectionContext *nca_fs_ctx = romfs_ctx->default_storage_ctx->nca_fs_ctx; - NcaContext *nca_ctx = nca_fs_ctx->nca_ctx; - - u64 title_id = nca_ctx->title_id; - u8 title_type = nca_ctx->title_type; + const char *dir_path = fs_browser_thread_data->dir_path; + const FsBrowserEntry *entries = fs_browser_thread_data->entries; + u32 entries_count = fs_browser_thread_data->entries_count; + const char *base_out_path = fs_browser_thread_data->base_out_path; - u64 free_space = 0; u32 dev_idx = g_storageMenuElementOption.selected; - u8 romfs_illegal_char_replace_type = (dev_idx != 0 ? RomFileSystemPathIllegalCharReplaceType_IllegalFsChars : RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly); buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); - if (romfs_thread_data->use_layeredfs_dir) - { - /* Only use base title IDs if we're dealing with patches. */ - title_id = (title_type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(title_id) : \ - (title_type == NcmContentMetaType_DataPatch ? titleGetAddOnContentIdByDataPatchId(title_id) : title_id)); - - filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "romfs"); - } else { - snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Extracted", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); - snprintf(romfs_path, MAX_ELEMENTS(romfs_path), "/%s #%u/%u", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx); - - TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); - filename = generateOutputTitleFileName(title_info, subdir, romfs_path); - } - - filename_len = (filename ? strlen(filename) : 0); - - if (!shared_thread_data->total_size || !buf1 || !buf2 || !filename) + if (!shared_thread_data->total_size || !dir_path || !*dir_path || !entries || !entries_count || !base_out_path || !*base_out_path || !buf1 || !buf2) { shared_thread_data->read_error = true; goto end; } - snprintf(romfs_path, MAX_ELEMENTS(romfs_path), "%s", filename); - if (dev_idx != 1) { - if (!utilsGetFileSystemStatsByPath(filename, NULL, &free_space)) + u64 free_space = 0; + + if (!utilsGetFileSystemStatsByPath(base_out_path, NULL, &free_space)) { consolePrint("failed to retrieve free space from selected device\n"); shared_thread_data->read_error = true; @@ -4599,22 +5560,69 @@ static void extractedRomFsReadThreadFunc(void *arg) shared_thread_data->read_error = true; } } else { - if (!usbStartExtractedFsDump(shared_thread_data->total_size, filename)) + if (!usbStartExtractedFsDump(shared_thread_data->total_size, base_out_path)) { consolePrint("failed to send extracted fs info to host\n"); shared_thread_data->read_error = true; } } - if (shared_thread_data->read_error) + if (!shared_thread_data->read_error) + { + /* Dump highlighted entries. */ + fsBrowserHighlightedEntriesReadThreadLoop(shared_thread_data, dir_path, entries, entries_count, base_out_path, buf1, buf2); + + if (!shared_thread_data->read_error && !shared_thread_data->write_error && !shared_thread_data->transfer_cancelled) + { + if (dev_idx == 1) usbEndExtractedFsDump(); + + consolePrint("successfully saved dumped data to \"%s\"\n", base_out_path); + consoleRefresh(); + } + } else { + condvarWakeAll(&g_writeCondvar); + } + +end: + if (buf2) free(buf2); + if (buf1) free(buf1); + + threadExit(); +} + +static bool fsBrowserHighlightedEntriesReadThreadLoop(SharedThreadData *shared_thread_data, const char *dir_path, const FsBrowserEntry *entries, u32 entries_count, const char *base_out_path, void *buf1, void *buf2) +{ + bool append_path_sep = (dir_path[strlen(dir_path) - 1] != '/'); + u32 dev_idx = g_storageMenuElementOption.selected; + bool is_topmost = (entries && entries_count); /* If entry data is provided, it means we're dealing with the topmost directory. */ + const char *dir_path_start = (strchr(dir_path, '/') + 1); + + char *tmp_path = NULL; + FILE *src = NULL; + + /* Allocate memory for our temporary path. */ + tmp_path = calloc(sizeof(char), FS_MAX_PATH); + if ((shared_thread_data->read_error = (tmp_path == NULL))) { + consolePrint("failed to allocate memory for path!\n"); condvarWakeAll(&g_writeCondvar); goto end; } - /* Loop through all file entries. */ - while(shared_thread_data->data_written < shared_thread_data->total_size && cur_entry_offset < romfs_ctx->file_table_size) + /* Get directory entries, if needed. */ + if (!is_topmost && (shared_thread_data->read_error = !fsBrowserGetDirEntries(dir_path, (FsBrowserEntry**)&entries, &entries_count))) { + condvarWakeAll(&g_writeCondvar); + goto end; + } + + /* Loop through all highlighted entries. */ + for(u32 i = 0; i < entries_count; i++) + { + /* Get current entry. */ + const FsBrowserEntry *entry = &(entries[i]); + if (is_topmost && !entry->highlight) continue; + /* Check if the transfer has been cancelled by the user. */ if (shared_thread_data->transfer_cancelled) { @@ -4636,19 +5644,39 @@ static void extractedRomFsReadThreadFunc(void *arg) { fclose(shared_thread_data->fp); shared_thread_data->fp = NULL; - utilsCommitSdCardFileSystemChanges(); + if (dev_idx == 0) utilsCommitSdCardFileSystemChanges(); } } - /* Retrieve RomFS file entry information and generate output path. */ - shared_thread_data->read_error = (!(romfs_file_entry = romfsGetFileEntryByOffset(romfs_ctx, cur_entry_offset)) || \ - !romfsGeneratePathFromFileEntry(romfs_ctx, romfs_file_entry, romfs_path + filename_len, sizeof(romfs_path) - filename_len, romfs_illegal_char_replace_type)); - if (shared_thread_data->read_error) + /* Generate input path. */ + snprintf(tmp_path, FS_MAX_PATH, "%s%s%s", dir_path, append_path_sep ? "/" : "", entry->dt.d_name); + + if (entry->dt.d_type == DT_DIR) + { + /* Dump directory. */ + if (!fsBrowserHighlightedEntriesReadThreadLoop(shared_thread_data, tmp_path, NULL, 0, base_out_path, buf1, buf2)) break; + continue; + } + + /* Open input file. */ + src = fopen(tmp_path, "rb"); + if ((shared_thread_data->read_error = (src == NULL))) { + consolePrint("failed to open file \"%s\" for reading!\n", tmp_path); condvarWakeAll(&g_writeCondvar); break; } + setvbuf(src, NULL, _IONBF, 0); + + /* Generate output path. */ + if (*dir_path_start) + { + snprintf(tmp_path, FS_MAX_PATH, "%s/%s/%s", base_out_path, dir_path_start, entry->dt.d_name); + } else { + snprintf(tmp_path, FS_MAX_PATH, "%s/%s", base_out_path, entry->dt.d_name); + } + if (dev_idx == 1) { /* Wait until the previous data chunk has been written */ @@ -4659,22 +5687,22 @@ static void extractedRomFsReadThreadFunc(void *arg) if (shared_thread_data->write_error) break; /* Send current file properties */ - shared_thread_data->read_error = !usbSendFileProperties(romfs_file_entry->size, romfs_path); + shared_thread_data->read_error = !usbSendFileProperties(entry->size, tmp_path); } else { /* Create directory tree. */ - utilsCreateDirectoryTree(romfs_path, false); + utilsCreateDirectoryTree(tmp_path, false); if (dev_idx == 0) { /* Create ConcatenationFile if we're dealing with a big file + SD card as the output storage. */ - if (romfs_file_entry->size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(romfs_path)) + if (entry->size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(tmp_path)) { - consolePrint("failed to create concatenation file for \"%s\"!\n", romfs_path); + consolePrint("failed to create concatenation file for \"%s\"!\n", tmp_path); shared_thread_data->read_error = true; } } else { /* Don't handle file chunks on FAT12/FAT16/FAT32 formatted UMS devices. */ - if (g_umsDevices[dev_idx - 2].fs_type < UsbHsFsDeviceFileSystemType_exFAT && romfs_file_entry->size > FAT32_FILESIZE_LIMIT) + if (g_umsDevices[dev_idx - 2].fs_type < UsbHsFsDeviceFileSystemType_exFAT && entry->size > FAT32_FILESIZE_LIMIT) { consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n"); shared_thread_data->read_error = true; @@ -4684,14 +5712,14 @@ static void extractedRomFsReadThreadFunc(void *arg) if (!shared_thread_data->read_error) { /* Open output file. */ - shared_thread_data->read_error = ((shared_thread_data->fp = fopen(romfs_path, "wb")) == NULL); + shared_thread_data->read_error = ((shared_thread_data->fp = fopen(tmp_path, "wb")) == NULL); if (!shared_thread_data->read_error) { /* Set file size. */ setvbuf(shared_thread_data->fp, NULL, _IONBF, 0); - ftruncate(fileno(shared_thread_data->fp), (off_t)romfs_file_entry->size); + ftruncate(fileno(shared_thread_data->fp), (off_t)entry->size); } else { - consolePrint("failed to open \"%s\" for writing!\n", romfs_path); + consolePrint("failed to open \"%s\" for writing!\n", tmp_path); } } } @@ -4702,9 +5730,10 @@ static void extractedRomFsReadThreadFunc(void *arg) break; } - for(u64 offset = 0, blksize = BLOCK_SIZE; offset < romfs_file_entry->size; offset += blksize) + /* Dump file. */ + for(u64 offset = 0, blksize = BLOCK_SIZE; offset < entry->size; offset += blksize) { - if (blksize > (romfs_file_entry->size - offset)) blksize = (romfs_file_entry->size - offset); + if (blksize > (entry->size - offset)) blksize = (entry->size - offset); /* Check if the transfer has been cancelled by the user. */ if (shared_thread_data->transfer_cancelled) @@ -4714,7 +5743,7 @@ static void extractedRomFsReadThreadFunc(void *arg) } /* Read current file data chunk. */ - shared_thread_data->read_error = !romfsReadFileEntryData(romfs_ctx, romfs_file_entry, buf1, blksize, offset); + shared_thread_data->read_error = (fread(buf1, 1, blksize, src) != blksize); if (shared_thread_data->read_error) { condvarWakeAll(&g_writeCondvar); @@ -4745,10 +5774,11 @@ static void extractedRomFsReadThreadFunc(void *arg) condvarWakeAll(&g_writeCondvar); } - if (shared_thread_data->read_error || shared_thread_data->write_error || shared_thread_data->transfer_cancelled) break; + /* Close input file. */ + fclose(src); + src = NULL; - /* Get the offset for the next file entry. */ - cur_entry_offset += ALIGN_UP(sizeof(RomFileSystemFileEntry) + romfs_file_entry->name_length, ROMFS_TABLE_ENTRY_ALIGNMENT); + if (shared_thread_data->read_error || shared_thread_data->write_error || shared_thread_data->transfer_cancelled) break; } if (!shared_thread_data->read_error && !shared_thread_data->write_error && !shared_thread_data->transfer_cancelled) @@ -4757,11 +5787,6 @@ static void extractedRomFsReadThreadFunc(void *arg) mutexLock(&g_fileMutex); if (shared_thread_data->data_size) condvarWait(&g_readCondvar, &g_fileMutex); mutexUnlock(&g_fileMutex); - - if (dev_idx == 1) usbEndExtractedFsDump(); - - consolePrint("successfully saved extracted romfs section data to \"%s\"\n", filename); - consoleRefresh(); } end: @@ -4772,17 +5797,18 @@ static void extractedRomFsReadThreadFunc(void *arg) if ((shared_thread_data->read_error || shared_thread_data->write_error || shared_thread_data->transfer_cancelled) && dev_idx != 1) { - utilsDeleteDirectoryRecursively(filename); + utilsDeleteDirectoryRecursively(base_out_path); if (dev_idx == 0) utilsCommitSdCardFileSystemChanges(); } } - if (filename) free(filename); + if (src) fclose(src); - if (buf2) free(buf2); - if (buf1) free(buf1); + if (!is_topmost && entries) free((FsBrowserEntry*)entries); - threadExit(); + if (tmp_path) free(tmp_path); + + return !shared_thread_data->read_error; } static void genericWriteThreadFunc(void *arg) diff --git a/include/core/nxdt_utils.h b/include/core/nxdt_utils.h index c319e344..39bf4518 100644 --- a/include/core/nxdt_utils.h +++ b/include/core/nxdt_utils.h @@ -157,6 +157,10 @@ bool utilsCreateConcatenationFile(const char *path); /// If 'create_last_element' is true, the last element from the provided path will be created as well. void utilsCreateDirectoryTree(const char *path, bool create_last_element); +/// Calculates the size of a directory by recursively traversing all of its child entries. +/// The provided path must be absolute and it must include the virtual device name it belongs to (e.g. "sdmc:/path/to/dir"). +bool utilsGetDirectorySize(const char *path, u64 *out_size); + /// Recursively deletes the directory located at the provided path and all of its contents. /// The provided path must be absolute and it must include the virtual device name it belongs to (e.g. "sdmc:/path/to/dir"). bool utilsDeleteDirectoryRecursively(const char *path); diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index c82cd2f6..059736b4 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -206,7 +206,7 @@ bool utilsInitializeResources(void) /* Load keyset. */ if (!keysLoadKeyset()) { - LOG_MSG_ERROR("Failed to load keyset!\nPlease update your keys file with Lockpick_RCM.\nYou can get an updated build at:" DISCORD_SERVER_URL); + LOG_MSG_ERROR("Failed to load keyset!\nPlease update your keys file with Lockpick_RCM.\nYou can get an updated build at: " DISCORD_SERVER_URL); break; } @@ -813,12 +813,84 @@ void utilsCreateDirectoryTree(const char *path, bool create_last_element) free(tmp); } +bool utilsGetDirectorySize(const char *path, u64 *out_size) +{ + u64 total_size = 0; + char *name_end = NULL, *entry_path = NULL; + DIR *dir = NULL; + struct dirent *entry = NULL; + struct stat st = {0}; + bool success = false; + + /* Sanity checks. */ + if (!path || !*path || !(name_end = strchr(path, ':')) || *(name_end + 1) != '/' || !*(name_end + 2) || !out_size) + { + LOG_MSG_ERROR("Invalid parameters!"); + return false; + } + + if (!(dir = opendir(path))) + { + LOG_MSG_ERROR("Failed to open directory \"%s\"! (%d).", path, errno); + goto end; + } + + if (!(entry_path = calloc(1, FS_MAX_PATH))) + { + LOG_MSG_ERROR("Failed to allocate memory for path buffer!"); + goto end; + } + + /* Read directory entries. */ + while((entry = readdir(dir))) + { + /* Skip current directory and parent directory entries. */ + if (!strcmp(".", entry->d_name) || !strcmp("..", entry->d_name)) continue; + + /* Generate path to the current entry. */ + snprintf(entry_path, FS_MAX_PATH, "%s/%s", path, entry->d_name); + + if (entry->d_type == DT_DIR) + { + /* Get directory size. */ + u64 dir_size = 0; + if (!utilsGetDirectorySize(entry_path, &dir_size)) goto end; + + /* Update size. */ + total_size += dir_size; + } else { + /* Get file properties. */ + if (stat(entry_path, &st) != 0) + { + LOG_MSG_ERROR("Failed to stat file \"%s\"! (%d).", entry_path, errno); + goto end; + } + + /* Update size. */ + total_size += st.st_size; + } + } + + /* Update output pointer. */ + *out_size = total_size; + + /* Update return value. */ + success = true; + +end: + if (entry_path) free(entry_path); + + if (dir) closedir(dir); + + return success; +} + bool utilsDeleteDirectoryRecursively(const char *path) { char *name_end = NULL, *entry_path = NULL; DIR *dir = NULL; struct dirent *entry = NULL; - bool success = true; + bool success = false; /* Sanity checks. */ if (!path || !*path || !(name_end = strchr(path, ':')) || *(name_end + 1) != '/' || !*(name_end + 2)) @@ -830,14 +902,12 @@ bool utilsDeleteDirectoryRecursively(const char *path) if (!(dir = opendir(path))) { LOG_MSG_ERROR("Failed to open directory \"%s\"! (%d).", path, errno); - success = false; goto end; } if (!(entry_path = calloc(1, FS_MAX_PATH))) { LOG_MSG_ERROR("Failed to allocate memory for path buffer!"); - success = false; goto end; } @@ -855,11 +925,7 @@ bool utilsDeleteDirectoryRecursively(const char *path) if (entry->d_type == DT_DIR) { /* Delete directory contents. */ - if (!utilsDeleteDirectoryRecursively(entry_path)) - { - success = false; - break; - } + if (!utilsDeleteDirectoryRecursively(entry_path)) goto end; /* Delete directory. */ status = rmdir(entry_path); @@ -870,20 +936,17 @@ bool utilsDeleteDirectoryRecursively(const char *path) if (status != 0) { - LOG_MSG_ERROR("Failed to delete \"%s\"! (%s, %d).", entry_path, entry->d_type == DT_DIR ? "dir" : "file", errno); - success = false; - break; + LOG_MSG_ERROR("Failed to delete %s \"%s\"! (%d).", entry->d_type == DT_DIR ? "directory" : "file", entry_path, errno); + goto end; } } - if (success) - { - closedir(dir); - dir = NULL; + /* Close topmost directory so we can delete it. */ + closedir(dir); + dir = NULL; - success = (rmdir(path) == 0); - if (!success) LOG_MSG_ERROR("Failed to delete topmost directory \"%s\"! (%d).", path, errno); - } + success = (rmdir(path) == 0); + if (!success) LOG_MSG_ERROR("Failed to delete topmost directory \"%s\"! (%d).", path, errno); end: if (entry_path) free(entry_path); diff --git a/source/devoptab/hfs_dev.c b/source/devoptab/hfs_dev.c index ae031d85..cb6b68b7 100644 --- a/source/devoptab/hfs_dev.c +++ b/source/devoptab/hfs_dev.c @@ -119,7 +119,7 @@ static int hfsdev_open(struct _reent *r, void *fd, const char *path, int flags, /* Get truncated path. */ if (!(path = hfsdev_get_truncated_path(r, path))) DEVOPTAB_EXIT; - LOG_MSG_DEBUG("Opening \"%s:/%s\" with flags 0x%X.", dev_ctx->name, path, flags); + //LOG_MSG_DEBUG("Opening \"%s:/%s\" with flags 0x%X.", dev_ctx->name, path, flags); /* Reset file descriptor. */ memset(file, 0, sizeof(HashFileSystemFileState)); @@ -140,7 +140,7 @@ static int hfsdev_close(struct _reent *r, void *fd) /* Sanity check. */ if (!file) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Closing \"%s:/%s\".", dev_ctx->name, file->name); + //LOG_MSG_DEBUG("Closing \"%s:/%s\".", dev_ctx->name, file->name); /* Reset file descriptor. */ memset(file, 0, sizeof(HashFileSystemFileState)); @@ -158,7 +158,7 @@ static ssize_t hfsdev_read(struct _reent *r, void *fd, char *ptr, size_t len) /* Sanity check. */ if (!file || !ptr || !len) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Reading 0x%lX byte(s) at offset 0x%lX from \"%s:/%s\".", len, file->offset, dev_ctx->name, file->name); + //LOG_MSG_DEBUG("Reading 0x%lX byte(s) at offset 0x%lX from \"%s:/%s\".", len, file->offset, dev_ctx->name, file->name); /* Read file data. */ if (!hfsReadEntryData(fs_ctx, file->hfs_entry, ptr, len, file->offset)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO); @@ -204,7 +204,7 @@ static off_t hfsdev_seek(struct _reent *r, void *fd, off_t pos, int dir) /* Don't allow positive seeks beyond the end of file. */ if (offset > (off_t)file->hfs_entry->size) DEVOPTAB_SET_ERROR_AND_EXIT(EOVERFLOW); - LOG_MSG_DEBUG("Seeking to offset 0x%lX from \"%s:/%s\".", offset, dev_ctx->name, file->name); + //LOG_MSG_DEBUG("Seeking to offset 0x%lX from \"%s:/%s\".", offset, dev_ctx->name, file->name); /* Adjust offset. */ file->offset = (u64)offset; @@ -221,7 +221,7 @@ static int hfsdev_fstat(struct _reent *r, void *fd, struct stat *st) /* Sanity check. */ if (!file || !st) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Getting file stats for \"%s:/%s\".", dev_ctx->name, file->name); + //LOG_MSG_DEBUG("Getting file stats for \"%s:/%s\".", dev_ctx->name, file->name); /* Fill stat info. */ hfsdev_fill_stat(st, file->index, file->hfs_entry, dev_ctx->mount_time); @@ -245,7 +245,7 @@ static int hfsdev_stat(struct _reent *r, const char *file, struct stat *st) /* Get truncated path. */ if (!(file = hfsdev_get_truncated_path(r, file))) DEVOPTAB_EXIT; - LOG_MSG_DEBUG("Getting file stats for \"%s:/%s\".", dev_ctx->name, file); + //LOG_MSG_DEBUG("Getting file stats for \"%s:/%s\".", dev_ctx->name, file); /* Get information about the requested Hash FS entry. */ if (!hfsGetEntryIndexByName(fs_ctx, file, &index) || !(hfs_entry = hfsGetEntryByIndex(fs_ctx, index))) DEVOPTAB_SET_ERROR_AND_EXIT(ENOENT); @@ -269,7 +269,7 @@ static DIR_ITER *hfsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char if (!(path = hfsdev_get_truncated_path(r, path))) DEVOPTAB_EXIT; if (*path) DEVOPTAB_SET_ERROR_AND_EXIT(ENOENT); - LOG_MSG_DEBUG("Opening directory \"%s:/\".", dev_ctx->name); + //LOG_MSG_DEBUG("Opening directory \"%s:/\".", dev_ctx->name); /* Reset directory state. */ memset(dir, 0, sizeof(HashFileSystemDirectoryState)); @@ -286,7 +286,7 @@ static int hfsdev_dirreset(struct _reent *r, DIR_ITER *dirState) { HFS_DEV_INIT_DIR_VARS; - LOG_MSG_DEBUG("Resetting directory state for \"%s:/\".", dev_ctx->name); + //LOG_MSG_DEBUG("Resetting directory state for \"%s:/\".", dev_ctx->name); /* Reset directory state. */ dir->state = 0; @@ -308,7 +308,7 @@ static int hfsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, /* Sanity check. */ if (!filename || !filestat) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Getting info for next directory entry in \"%s:/\" (state %u, index %u).", dev_ctx->name, dir->state, dir->index); + //LOG_MSG_DEBUG("Getting info for next directory entry in \"%s:/\" (state %u, index %u).", dev_ctx->name, dir->state, dir->index); if (dir->state < 2) { @@ -352,7 +352,7 @@ static int hfsdev_dirclose(struct _reent *r, DIR_ITER *dirState) { HFS_DEV_INIT_DIR_VARS; - LOG_MSG_DEBUG("Closing directory \"%s:/\".", dev_ctx->name); + //LOG_MSG_DEBUG("Closing directory \"%s:/\".", dev_ctx->name); /* Reset directory state. */ memset(dir, 0, sizeof(HashFileSystemDirectoryState)); @@ -374,7 +374,7 @@ static int hfsdev_statvfs(struct _reent *r, const char *path, struct statvfs *bu /* Sanity check. */ if (!buf) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Getting filesystem stats for \"%s:\"", dev_ctx->name); + //LOG_MSG_DEBUG("Getting filesystem stats for \"%s:\"", dev_ctx->name); /* Get Hash FS total data size. */ if (!hfsGetTotalDataSize(fs_ctx, &ext_fs_size)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO); @@ -411,7 +411,7 @@ static const char *hfsdev_get_truncated_path(struct _reent *r, const char *path) if (!r || !path || !*path) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Input path: \"%s\".", path); + //LOG_MSG_DEBUG("Input path: \"%s\".", path); /* Move the path pointer to the start of the actual path. */ do { @@ -445,7 +445,7 @@ static const char *hfsdev_get_truncated_path(struct _reent *r, const char *path) if (!len && !path_sep_skipped) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); if (len >= FS_MAX_PATH) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG); - LOG_MSG_DEBUG("Truncated path: \"%s\".", path); + //LOG_MSG_DEBUG("Truncated path: \"%s\".", path); end: DEVOPTAB_RETURN_PTR(path); diff --git a/source/devoptab/nxdt_romfs_dev.c b/source/devoptab/nxdt_romfs_dev.c index 686cf4f8..18c852c6 100644 --- a/source/devoptab/nxdt_romfs_dev.c +++ b/source/devoptab/nxdt_romfs_dev.c @@ -125,7 +125,7 @@ static int romfsdev_open(struct _reent *r, void *fd, const char *path, int flags /* Get truncated path. */ if (!(path = romfsdev_get_truncated_path(r, path))) DEVOPTAB_EXIT; - LOG_MSG_DEBUG("Opening \"%s:%s\" with flags 0x%X.", dev_ctx->name, path, flags); + //LOG_MSG_DEBUG("Opening \"%s:%s\" with flags 0x%X.", dev_ctx->name, path, flags); /* Reset file descriptor. */ memset(file, 0, sizeof(RomFileSystemFileState)); @@ -145,7 +145,7 @@ static int romfsdev_close(struct _reent *r, void *fd) /* Sanity check. */ if (!file) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Closing file \"%.*s\" from \"%s:\".", (int)file->file_entry->name_length, file->file_entry->name, dev_ctx->name); + //LOG_MSG_DEBUG("Closing file \"%.*s\" from \"%s:\".", (int)file->file_entry->name_length, file->file_entry->name, dev_ctx->name); /* Reset file descriptor. */ memset(file, 0, sizeof(RomFileSystemFileState)); @@ -163,8 +163,8 @@ static ssize_t romfsdev_read(struct _reent *r, void *fd, char *ptr, size_t len) /* Sanity check. */ if (!file || !ptr || !len) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Reading 0x%lX byte(s) at offset 0x%lX from file \"%.*s\" in \"%s:\".", len, file->data_offset, (int)file->file_entry->name_length, file->file_entry->name, \ - dev_ctx->name); + /*LOG_MSG_DEBUG("Reading 0x%lX byte(s) at offset 0x%lX from file \"%.*s\" in \"%s:\".", len, file->data_offset, (int)file->file_entry->name_length, file->file_entry->name, \ + dev_ctx->name);*/ /* Read file data. */ if (!romfsReadFileEntryData(fs_ctx, file->file_entry, ptr, len, file->data_offset)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO); @@ -210,7 +210,7 @@ static off_t romfsdev_seek(struct _reent *r, void *fd, off_t pos, int dir) /* Don't allow positive seeks beyond the end of file. */ if (offset > (off_t)file->file_entry->size) DEVOPTAB_SET_ERROR_AND_EXIT(EOVERFLOW); - LOG_MSG_DEBUG("Seeking to offset 0x%lX from file \"%.*s\" in \"%s:\".", offset, (int)file->file_entry->name_length, file->file_entry->name, dev_ctx->name); + //LOG_MSG_DEBUG("Seeking to offset 0x%lX from file \"%.*s\" in \"%s:\".", offset, (int)file->file_entry->name_length, file->file_entry->name, dev_ctx->name); /* Adjust offset. */ file->data_offset = (u64)offset; @@ -228,7 +228,7 @@ static int romfsdev_fstat(struct _reent *r, void *fd, struct stat *st) /* Sanity check. */ if (!file || !st) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Getting stats for file \"%.*s\" in \"%s:\".", (int)file->file_entry->name_length, file->file_entry->name, dev_ctx->name); + //LOG_MSG_DEBUG("Getting stats for file \"%.*s\" in \"%s:\".", (int)file->file_entry->name_length, file->file_entry->name, dev_ctx->name); /* Fill stat info. */ romfsdev_fill_file_stat(st, fs_ctx, file->file_entry, dev_ctx->mount_time); @@ -251,7 +251,7 @@ static int romfsdev_stat(struct _reent *r, const char *file, struct stat *st) /* Get truncated path. */ if (!(file = romfsdev_get_truncated_path(r, file))) DEVOPTAB_EXIT; - LOG_MSG_DEBUG("Getting file stats for \"%s:%s\".", dev_ctx->name, file); + //LOG_MSG_DEBUG("Getting file stats for \"%s:%s\".", dev_ctx->name, file); /* Get information about the requested RomFS file entry. */ if (!(file_entry = romfsGetFileEntryByPath(fs_ctx, file))) DEVOPTAB_SET_ERROR_AND_EXIT(ENOENT); @@ -274,7 +274,7 @@ static DIR_ITER *romfsdev_diropen(struct _reent *r, DIR_ITER *dirState, const ch /* Get truncated path. */ if (!(path = romfsdev_get_truncated_path(r, path))) DEVOPTAB_EXIT; - LOG_MSG_DEBUG("Opening directory \"%s:%s\".", dev_ctx->name, path); + //LOG_MSG_DEBUG("Opening directory \"%s:%s\".", dev_ctx->name, path); /* Reset directory state. */ memset(dir, 0, sizeof(RomFileSystemDirectoryState)); @@ -297,7 +297,7 @@ static int romfsdev_dirreset(struct _reent *r, DIR_ITER *dirState) { ROMFS_DEV_INIT_DIR_VARS; - LOG_MSG_DEBUG("Resetting state for directory \"%.*s\" in \"%s:\".", (int)dir->dir_entry->name_length, dir->dir_entry->name, dev_ctx->name); + //LOG_MSG_DEBUG("Resetting state for directory \"%.*s\" in \"%s:\".", (int)dir->dir_entry->name_length, dir->dir_entry->name, dev_ctx->name); /* Reset directory state. */ dir->state = 0; @@ -317,8 +317,8 @@ static int romfsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename /* Sanity check. */ if (!filename || !filestat) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Getting info for next entry from directory \"%.*s\" in \"%s:\" (state %u, cur_dir_offset 0x%lX, cur_file_offset 0x%lX).", \ - (int)dir->dir_entry->name_length, dir->dir_entry->name, dev_ctx->name, dir->state, dir->cur_dir_offset, dir->cur_file_offset); + /*LOG_MSG_DEBUG("Getting info for next entry from directory \"%.*s\" in \"%s:\" (state %u, cur_dir_offset 0x%lX, cur_file_offset 0x%lX).", \ + (int)dir->dir_entry->name_length, dir->dir_entry->name, dev_ctx->name, dir->state, dir->cur_dir_offset, dir->cur_file_offset);*/ if (dir->state < 2) { @@ -381,7 +381,7 @@ static int romfsdev_dirclose(struct _reent *r, DIR_ITER *dirState) { ROMFS_DEV_INIT_DIR_VARS; - LOG_MSG_DEBUG("Closing directory \"%.*s\" in \"%s:\".", (int)dir->dir_entry->name_length, dir->dir_entry->name, dev_ctx->name); + //LOG_MSG_DEBUG("Closing directory \"%.*s\" in \"%s:\".", (int)dir->dir_entry->name_length, dir->dir_entry->name, dev_ctx->name); /* Reset directory state. */ memset(dir, 0, sizeof(RomFileSystemDirectoryState)); @@ -403,7 +403,7 @@ static int romfsdev_statvfs(struct _reent *r, const char *path, struct statvfs * /* Sanity check. */ if (!buf) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Getting filesystem stats for \"%s:\"", dev_ctx->name); + //LOG_MSG_DEBUG("Getting filesystem stats for \"%s:\"", dev_ctx->name); /* Get RomFS total data size. */ if (!romfsGetTotalDataSize(fs_ctx, false, &ext_fs_size)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO); @@ -439,7 +439,7 @@ static const char *romfsdev_get_truncated_path(struct _reent *r, const char *pat if (!r || !path || !*path) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Input path: \"%s\".", path); + //LOG_MSG_DEBUG("Input path: \"%s\".", path); /* Move the path pointer to the start of the actual path. */ do { @@ -468,7 +468,7 @@ static const char *romfsdev_get_truncated_path(struct _reent *r, const char *pat len = strlen(path); if (len >= FS_MAX_PATH) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG); - LOG_MSG_DEBUG("Truncated path: \"%s\".", path); + //LOG_MSG_DEBUG("Truncated path: \"%s\".", path); end: DEVOPTAB_RETURN_PTR(path); diff --git a/source/devoptab/pfs_dev.c b/source/devoptab/pfs_dev.c index 751cda8c..5aa9e5ad 100644 --- a/source/devoptab/pfs_dev.c +++ b/source/devoptab/pfs_dev.c @@ -119,7 +119,7 @@ static int pfsdev_open(struct _reent *r, void *fd, const char *path, int flags, /* Get truncated path. */ if (!(path = pfsdev_get_truncated_path(r, path))) DEVOPTAB_EXIT; - LOG_MSG_DEBUG("Opening \"%s:/%s\" with flags 0x%X.", dev_ctx->name, path, flags); + //LOG_MSG_DEBUG("Opening \"%s:/%s\" with flags 0x%X.", dev_ctx->name, path, flags); /* Reset file descriptor. */ memset(file, 0, sizeof(PartitionFileSystemFileState)); @@ -140,7 +140,7 @@ static int pfsdev_close(struct _reent *r, void *fd) /* Sanity check. */ if (!file) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Closing \"%s:/%s\".", dev_ctx->name, file->name); + //LOG_MSG_DEBUG("Closing \"%s:/%s\".", dev_ctx->name, file->name); /* Reset file descriptor. */ memset(file, 0, sizeof(PartitionFileSystemFileState)); @@ -158,7 +158,7 @@ static ssize_t pfsdev_read(struct _reent *r, void *fd, char *ptr, size_t len) /* Sanity check. */ if (!file || !ptr || !len) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Reading 0x%lX byte(s) at offset 0x%lX from \"%s:/%s\".", len, file->offset, dev_ctx->name, file->name); + //LOG_MSG_DEBUG("Reading 0x%lX byte(s) at offset 0x%lX from \"%s:/%s\".", len, file->offset, dev_ctx->name, file->name); /* Read file data. */ if (!pfsReadEntryData(fs_ctx, file->pfs_entry, ptr, len, file->offset)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO); @@ -204,7 +204,7 @@ static off_t pfsdev_seek(struct _reent *r, void *fd, off_t pos, int dir) /* Don't allow positive seeks beyond the end of file. */ if (offset > (off_t)file->pfs_entry->size) DEVOPTAB_SET_ERROR_AND_EXIT(EOVERFLOW); - LOG_MSG_DEBUG("Seeking to offset 0x%lX from \"%s:/%s\".", offset, dev_ctx->name, file->name); + //LOG_MSG_DEBUG("Seeking to offset 0x%lX from \"%s:/%s\".", offset, dev_ctx->name, file->name); /* Adjust offset. */ file->offset = (u64)offset; @@ -221,7 +221,7 @@ static int pfsdev_fstat(struct _reent *r, void *fd, struct stat *st) /* Sanity check. */ if (!file || !st) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Getting file stats for \"%s:/%s\".", dev_ctx->name, file->name); + //LOG_MSG_DEBUG("Getting file stats for \"%s:/%s\".", dev_ctx->name, file->name); /* Fill stat info. */ pfsdev_fill_stat(st, file->index, file->pfs_entry, dev_ctx->mount_time); @@ -245,7 +245,7 @@ static int pfsdev_stat(struct _reent *r, const char *file, struct stat *st) /* Get truncated path. */ if (!(file = pfsdev_get_truncated_path(r, file))) DEVOPTAB_EXIT; - LOG_MSG_DEBUG("Getting file stats for \"%s:/%s\".", dev_ctx->name, file); + //LOG_MSG_DEBUG("Getting file stats for \"%s:/%s\".", dev_ctx->name, file); /* Get information about the requested Partition FS entry. */ if (!pfsGetEntryIndexByName(fs_ctx, file, &index) || !(pfs_entry = pfsGetEntryByIndex(fs_ctx, index))) DEVOPTAB_SET_ERROR_AND_EXIT(ENOENT); @@ -269,7 +269,7 @@ static DIR_ITER *pfsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char if (!(path = pfsdev_get_truncated_path(r, path))) DEVOPTAB_EXIT; if (*path) DEVOPTAB_SET_ERROR_AND_EXIT(ENOENT); - LOG_MSG_DEBUG("Opening directory \"%s:/\".", dev_ctx->name); + //LOG_MSG_DEBUG("Opening directory \"%s:/\".", dev_ctx->name); /* Reset directory state. */ memset(dir, 0, sizeof(PartitionFileSystemDirectoryState)); @@ -286,7 +286,7 @@ static int pfsdev_dirreset(struct _reent *r, DIR_ITER *dirState) { PFS_DEV_INIT_DIR_VARS; - LOG_MSG_DEBUG("Resetting directory state for \"%s:/\".", dev_ctx->name); + //LOG_MSG_DEBUG("Resetting directory state for \"%s:/\".", dev_ctx->name); /* Reset directory state. */ dir->state = 0; @@ -308,7 +308,7 @@ static int pfsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, /* Sanity check. */ if (!filename || !filestat) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Getting info for next directory entry in \"%s:/\" (state %u, index %u).", dev_ctx->name, dir->state, dir->index); + //LOG_MSG_DEBUG("Getting info for next directory entry in \"%s:/\" (state %u, index %u).", dev_ctx->name, dir->state, dir->index); if (dir->state < 2) { @@ -352,7 +352,7 @@ static int pfsdev_dirclose(struct _reent *r, DIR_ITER *dirState) { PFS_DEV_INIT_DIR_VARS; - LOG_MSG_DEBUG("Closing directory \"%s:/\".", dev_ctx->name); + //LOG_MSG_DEBUG("Closing directory \"%s:/\".", dev_ctx->name); /* Reset directory state. */ memset(dir, 0, sizeof(PartitionFileSystemDirectoryState)); @@ -374,7 +374,7 @@ static int pfsdev_statvfs(struct _reent *r, const char *path, struct statvfs *bu /* Sanity check. */ if (!buf) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Getting filesystem stats for \"%s:\"", dev_ctx->name); + //LOG_MSG_DEBUG("Getting filesystem stats for \"%s:\"", dev_ctx->name); /* Get Partition FS total data size. */ if (!pfsGetTotalDataSize(fs_ctx, &ext_fs_size)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO); @@ -411,7 +411,7 @@ static const char *pfsdev_get_truncated_path(struct _reent *r, const char *path) if (!r || !path || !*path) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); - LOG_MSG_DEBUG("Input path: \"%s\".", path); + //LOG_MSG_DEBUG("Input path: \"%s\".", path); /* Move the path pointer to the start of the actual path. */ do { @@ -445,7 +445,7 @@ static const char *pfsdev_get_truncated_path(struct _reent *r, const char *path) if (!len && !path_sep_skipped) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); if (len >= FS_MAX_PATH) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG); - LOG_MSG_DEBUG("Truncated path: \"%s\".", path); + //LOG_MSG_DEBUG("Truncated path: \"%s\".", path); end: DEVOPTAB_RETURN_PTR(path);