From fb58d20fe3a4aebe3ce7d1e25bea1c0c7025ef9e Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Wed, 24 May 2023 21:05:34 +0200 Subject: [PATCH] I'm a terrible person and an even worse developer. And I don't need anyone to tell me so, thank you very much. * PoC: remove gc_dumper and nsp_dumper PoC; create nxdt_rw_poc with all gc_dumper and nsp_dumper capabilities + standalone ticket dumping + raw NCA dumping; use ftruncate() to set output file sizes whenever possible. PoC code is a mess, as always. Expect the features from the rest of the PoCs to be implemented into nxdt_rw_poc soon. * workflow: temporarily disable borealis build generation; comment out manual installation of up-to-date packages from Leseratte's mirrors because the latest devkitA64 Docker image has them all. * borealis: update to fix building issues with latest devkitA64. * bfttf: error out on invalid NCA signatures. * config: save configuration to the current working directory; parse and validate new "gamecard/write_raw_hfs_partition" flag. * defines: remove CONFIG_PATH macro; rename CONFIG_FILE_NAME. * gamecard: rename fs_ctx -> hfs_ctx everywhere; use HFS function calls to retrieve partition names. * hfs: move GameCardHashFileSystemPartitionType enum from gamecard.h and rename it to HashFileSystemPartitionType; add hfsIsValidContext(); add hfsGetPartitionNameString(). * nca/npdm: update comments to reflect latest HOS version. * nxdt_bfsar: always generate absolute SD card paths with the device name; error out on an invalid NCA signature. * nxdt_includes: include dirent.h; refactor Version struct to make it a union of all known *Version structs. * nxdt_log: don't write session separator if the logfile is empty. * nxdt_utils: log appletIsGamePlayRecordingSupported() errors; add utilsDeleteDirectoryRecursively(). * rsa: provide clearer function descriptions in header file. * services: handle usb:ds initialization. * tik: update tikConvertPersonalizedTicketToCommonTicket() to allow NULL input pointers as raw certificate chain arguments (much needed for standalone ticket dumping). * title: add titleGetApplicationIdByMetaKey(). * usb: refactor interface (de)initialization code; slightly improve ABI usage (console-side only); redefine ABI version field in StartSession command blocks; upgrade ABI to v1.1. * FatFs: rename DIR -> FDIR to avoid conflicts with definitions from stdlib's dirent.h. * gamecard_tab: display package ID from the inserted gamecard; fix displayed version numbers from bundled system updates below 3.0.0. * todo: add notes about creating devoptab devices for HFS/PFS/RomFS file tree dumping. --- .github/workflows/rewrite.yml | 40 +- code_templates/gc_dumper.c | 1280 -------- code_templates/nsp_dumper.c | 1490 --------- code_templates/nxdt_rw_poc.c | 4197 ++++++++++++++++++++++++++ code_templates/sd_romfs_dumper.c | 7 +- code_templates/system_title_dumper.c | 6 + code_templates/usb_romfs_dumper.c | 6 +- code_templates/xml_generator.c | 4 +- host/README.md | 20 +- host/nxdt_host.py | 454 ++- include/core/gamecard.h | 37 +- include/core/hfs.h | 28 +- include/core/nca.h | 8 +- include/core/npdm.h | 2 +- include/core/nxdt_includes.h | 45 +- include/core/nxdt_utils.h | 4 + include/core/romfs.h | 5 + include/core/rsa.h | 6 +- include/core/tik.h | 7 +- include/core/usb.h | 26 +- include/defines.h | 2 +- include/fatfs/ff.h | 14 +- libs/borealis | 2 +- romfs/default_config.json | 3 +- romfs/i18n/en-US/gamecard_tab.json | 3 +- source/core/bfttf.c | 8 +- source/core/cert.c | 2 +- source/core/config.c | 35 +- source/core/gamecard.c | 195 +- source/core/hfs.c | 53 +- source/core/nca.c | 6 +- source/core/nxdt_bfsar.c | 8 +- source/core/nxdt_log.c | 16 +- source/core/nxdt_utils.c | 88 +- source/core/services.c | 3 +- source/core/tik.c | 42 +- source/core/title.c | 56 +- source/core/usb.c | 456 +-- source/fatfs/ff.c | 76 +- source/gamecard_tab.cpp | 55 +- todo.txt | 6 +- 41 files changed, 5329 insertions(+), 3472 deletions(-) delete mode 100644 code_templates/gc_dumper.c delete mode 100644 code_templates/nsp_dumper.c create mode 100644 code_templates/nxdt_rw_poc.c diff --git a/.github/workflows/rewrite.yml b/.github/workflows/rewrite.yml index b017843..e6c5708 100644 --- a/.github/workflows/rewrite.yml +++ b/.github/workflows/rewrite.yml @@ -32,12 +32,12 @@ jobs: run: | sudo -n apt-get update sudo -n apt-get upgrade -y patch autoconf automake tar bzip2 diffutils pkgconf fakeroot p7zip-full git file - sudo -n dkp-pacman --noconfirm -U \ - "https://wii.leseratte10.de/devkitPro/switch/switch-libjson-c-0.16-1-any.pkg.tar.xz" \ - "https://wii.leseratte10.de/devkitPro/switch/switch-libpng-1.6.39-1-any.pkg.tar.xz" \ - "https://wii.leseratte10.de/devkitPro/switch/switch-zlib-1.2.13-1-any.pkg.tar.xz" \ - "https://wii.leseratte10.de/devkitPro/other-stuff/dkp-toolchain-vars-1.0.2-1-any.pkg.tar.xz" \ - "https://wii.leseratte10.de/devkitPro/cmake/switch-cmake-1.5.0-1-any.pkg.tar.xz" + # sudo -n dkp-pacman --noconfirm -U \ + # "https://wii.leseratte10.de/devkitPro/switch/switch-libjson-c-0.16-1-any.pkg.tar.xz" \ + # "https://wii.leseratte10.de/devkitPro/switch/switch-libpng-1.6.39-2-any.pkg.tar.zst" \ + # "https://wii.leseratte10.de/devkitPro/switch/switch-zlib-1.2.13-1-any.pkg.tar.xz" \ + # "https://wii.leseratte10.de/devkitPro/other-stuff/dkp-toolchain-vars-1.0.3-2-any.pkg.tar.zst" \ + # "https://wii.leseratte10.de/devkitPro/cmake/switch-cmake-1.5.0-1-any.pkg.tar.xz" - name: Silence all git safe directory warnings run: git config --system --add safe.directory '*' @@ -76,10 +76,10 @@ jobs: echo "nxdt_commit=$(git rev-parse --short HEAD)" >> $GITHUB_ENV ./build.sh - - name: Build nxdumptool-rewrite GUI binary - run: | - cd "$GITHUB_WORKSPACE/nxdumptool" - make -j$(nproc) + #- name: Build nxdumptool-rewrite GUI binary + # run: | + # cd "$GITHUB_WORKSPACE/nxdumptool" + # make -j$(nproc) - uses: actions/upload-artifact@v3 with: @@ -93,14 +93,14 @@ jobs: path: nxdumptool/nxdumptool-rewrite_poc_${{ env.nxdt_commit }}-Debug_ELFs.7z if-no-files-found: error - - uses: actions/upload-artifact@v3 - with: - name: nxdumptool-rewrite-${{ env.nxdt_commit }}-WIP_UI.nro - path: nxdumptool/nxdumptool.nro - if-no-files-found: error + #- uses: actions/upload-artifact@v3 + # with: + # name: nxdumptool-rewrite-${{ env.nxdt_commit }}-WIP_UI.nro + # path: nxdumptool/nxdumptool.nro + # if-no-files-found: error - - uses: actions/upload-artifact@v3 - with: - name: nxdumptool-rewrite-${{ env.nxdt_commit }}-WIP_UI.elf - path: nxdumptool/nxdumptool.elf - if-no-files-found: error + #- uses: actions/upload-artifact@v3 + # with: + # name: nxdumptool-rewrite-${{ env.nxdt_commit }}-WIP_UI.elf + # path: nxdumptool/nxdumptool.elf + # if-no-files-found: error diff --git a/code_templates/gc_dumper.c b/code_templates/gc_dumper.c deleted file mode 100644 index 8c63cae..0000000 --- a/code_templates/gc_dumper.c +++ /dev/null @@ -1,1280 +0,0 @@ -/* - * main.c - * - * Copyright (c) 2020-2023, DarkMatterCore . - * - * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). - * - * nxdumptool is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * nxdumptool is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "nxdt_utils.h" -#include "gamecard.h" -#include "usb.h" -#include "title.h" - -#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE - -bool g_borealisInitialized = false; - -static PadState g_padState = {0}; - -/* Type definitions. */ - -typedef void (*MenuElementOptionFunction)(u32 idx); - -typedef struct { - u32 selected; ///< Used to keep track of the selected option. - MenuElementOptionFunction options_func; ///< Pointer to a function to be called each time a new option is selected. Should be set to NULL if not used. - char **options; ///< Pointer to multiple char pointers with strings representing options. Last element must be set to NULL. -} MenuElementOption; - -typedef bool (*MenuElementFunction)(void); - -typedef struct { - const char *str; ///< Pointer to a string to be printed for this menu element. - void *child_menu; ///< Pointer to a child Menu element. Must be set to NULL if task_func != NULL. - MenuElementFunction task_func; ///< Pointer to a function to be called by this element. Must be set to NULL if child_menu != NULL. - MenuElementOption *element_options; ///< Options for this menu element. Should be set to NULL if not used. -} MenuElement; - -typedef struct _Menu { - struct _Menu *parent; ///< Set to NULL in the root menu element. - u32 selected, scroll; ///< Used to keep track of the selected element and scroll values. - MenuElement **elements; ///< Element info from this menu. Last element must be set to NULL. -} Menu; - -typedef struct -{ - void *data; - size_t data_size; - size_t data_written; - size_t total_size; - bool read_error; - bool write_error; - bool transfer_cancelled; - u32 xci_crc, full_xci_crc; - FILE *fp; -} ThreadSharedData; - -/* Function prototypes. */ - -static void utilsScanPads(void); -static u64 utilsGetButtonsDown(void); -static u64 utilsGetButtonsHeld(void); -static void utilsWaitForButtonPress(u64 flag); - -static void consolePrint(const char *text, ...); -static void consoleRefresh(void); - -static u32 menuGetElementCount(const Menu *menu); - -void freeStorageList(void); -void updateStorageList(void); - -NX_INLINE bool useUsbHost(void); - -static bool waitForGameCard(void); -static bool waitForUsb(void); - -static void generateDumpTxt(void); -static bool saveDumpTxt(void); - -static char *generateOutputFileName(const char *extension); - -static bool saveGameCardSpecificData(void); -static bool saveGameCardCertificate(void); -static bool saveGameCardInitialData(void); -static bool saveGameCardIdSet(void); -static bool saveGameCardImage(void); -static bool saveConsoleLafwBlob(void); - -static void changeKeyAreaOption(u32 idx); -static void changeCertificateOption(u32 idx); -static void changeTrimOption(u32 idx); -static void changeCrcOption(u32 idx); - -static void read_thread_func(void *arg); -static void write_thread_func(void *arg); - -/* Global variables. */ - -static bool g_appendKeyArea = true, g_keepCertificate = false, g_trimDump = false, g_calcCrc = false; - -static char *g_xciOptions[] = { "no", "yes", NULL }; - -static MenuElement *g_xciMenuElements[] = { - &(MenuElement){ - .str = "start dump", - .child_menu = NULL, - .task_func = &saveGameCardImage, - .element_options = NULL - }, - &(MenuElement){ - .str = "prepend key area", - .child_menu = NULL, - .task_func = NULL, - .element_options = &(MenuElementOption){ - .selected = 1, - .options_func = &changeKeyAreaOption, - .options = g_xciOptions - } - }, - &(MenuElement){ - .str = "keep certificate", - .child_menu = NULL, - .task_func = NULL, - .element_options = &(MenuElementOption){ - .selected = 0, - .options_func = &changeCertificateOption, - .options = g_xciOptions - } - }, - &(MenuElement){ - .str = "trim dump", - .child_menu = NULL, - .task_func = NULL, - .element_options = &(MenuElementOption){ - .selected = 0, - .options_func = &changeTrimOption, - .options = g_xciOptions - } - }, - &(MenuElement){ - .str = "calculate crc32", - .child_menu = NULL, - .task_func = NULL, - .element_options = &(MenuElementOption){ - .selected = 0, - .options_func = &changeCrcOption, - .options = g_xciOptions - } - }, - NULL -}; - -static Menu g_xciMenu = { - .parent = NULL, - .selected = 0, - .scroll = 0, - .elements = g_xciMenuElements -}; - -static MenuElementOption g_storageMenuElementOption = { - .selected = 0, - .options_func = NULL, - .options = NULL -}; - -static MenuElement *g_rootMenuElements[] = { - &(MenuElement){ - .str = "dump gamecard xci", - .child_menu = &g_xciMenu, - .task_func = NULL, - .element_options = NULL - }, - &(MenuElement){ - .str = "dump gamecard certificate", - .child_menu = NULL, - .task_func = &saveGameCardCertificate, - .element_options = NULL - }, - &(MenuElement){ - .str = "dump gamecard initial data", - .child_menu = NULL, - .task_func = &saveGameCardInitialData, - .element_options = NULL - }, - &(MenuElement){ - .str = "dump gamecard id set", - .child_menu = NULL, - .task_func = &saveGameCardIdSet, - .element_options = NULL - }, - &(MenuElement){ - .str = "dump gamecard specific data", - .child_menu = NULL, - .task_func = &saveGameCardSpecificData, - .element_options = NULL - }, - &(MenuElement){ - .str = "dump console lafw blob", - .child_menu = NULL, - .task_func = &saveConsoleLafwBlob, - .element_options = NULL - }, - &(MenuElement){ - .str = "output storage", - .child_menu = NULL, - .task_func = NULL, - .element_options = &g_storageMenuElementOption - }, - NULL -}; - -static Menu g_rootMenu = { - .parent = NULL, - .selected = 0, - .scroll = 0, - .elements = g_rootMenuElements -}; - -static Mutex g_conMutex = 0, g_fileMutex = 0; -static CondVar g_readCondvar = 0, g_writeCondvar = 0; - -static char path[FS_MAX_PATH] = {0}, txt_info[FS_MAX_PATH] = {0}; - -static bool g_appletStatus = true; - -static UsbHsFsDevice *g_umsDevices = NULL; -static u32 g_umsDeviceCount = 0; -static char **g_storageOptions = NULL; - -int main(int argc, char *argv[]) -{ - int ret = 0; - - Menu *cur_menu = &g_rootMenu; - u32 element_count = menuGetElementCount(cur_menu), page_size = 30; - - if (!utilsInitializeResources(argc, (const char**)argv)) - { - ret = -1; - goto out; - } - - /* Configure input. */ - /* Up to 8 different, full controller inputs. */ - /* Individual Joy-Cons not supported. */ - padConfigureInput(8, HidNpadStyleSet_NpadFullCtrl); - padInitializeWithMask(&g_padState, 0x1000000FFUL); - - consoleInit(NULL); - - updateStorageList(); - - while(appletMainLoop()) - { - consoleClear(); - consolePrint("\npress b to %s.\n", cur_menu->parent ? "go back" : "exit"); - if (g_umsDeviceCount) consolePrint("press x to safely remove all ums devices.\n\n"); - - u32 limit = (cur_menu->scroll + page_size); - MenuElement *selected_element = cur_menu->elements[cur_menu->selected]; - MenuElementOption *selected_element_options = selected_element->element_options; - - for(u32 i = cur_menu->scroll; cur_menu->elements[i] && i < element_count; i++) - { - if (i >= limit) break; - - MenuElement *cur_element = cur_menu->elements[i]; - MenuElementOption *cur_options = cur_menu->elements[i]->element_options; - - consolePrint("%s%s", i == cur_menu->selected ? " -> " : " ", cur_element->str); - - if (cur_options) - { - consolePrint(": "); - if (cur_options->selected > 0) consolePrint("< "); - consolePrint("%s", cur_options->options[cur_options->selected]); - if (cur_options->options[cur_options->selected + 1]) consolePrint(" >"); - } - - consolePrint("\n"); - } - - consolePrint("\n"); - consoleRefresh(); - - bool data_update = false; - u64 btn_down = 0, btn_held = 0; - - while((g_appletStatus = appletMainLoop())) - { - utilsScanPads(); - btn_down = utilsGetButtonsDown(); - btn_held = utilsGetButtonsHeld(); - if (btn_down || btn_held) break; - - if (umsIsDeviceInfoUpdated()) - { - updateStorageList(); - data_update = true; - break; - } - } - - if (!g_appletStatus) break; - - if (data_update) continue; - - if (btn_down & HidNpadButton_A) - { - Menu *child_menu = (Menu*)selected_element->child_menu; - - if (child_menu) - { - child_menu->parent = cur_menu; - cur_menu = child_menu; - element_count = menuGetElementCount(cur_menu); - } else - if (selected_element->task_func) - { - /* Wait for gamecard. */ - if (!waitForGameCard()) - { - if (g_appletStatus) continue; - break; - } - - /* Wait for USB session. */ - if (useUsbHost() && !waitForUsb()) break; - - /* Generate dump text. */ - generateDumpTxt(); - - /* Run task. */ - utilsSetLongRunningProcessState(true); - - if (selected_element->task_func()) - { - saveDumpTxt(); - if (!useUsbHost()) updateStorageList(); // update free space - } - - utilsSetLongRunningProcessState(false); - - /* Display prompt. */ - consolePrint("press any button to continue"); - utilsWaitForButtonPress(0); - } - } else - if ((btn_down & HidNpadButton_Down) || (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown))) - { - cur_menu->selected++; - - if (!cur_menu->elements[cur_menu->selected]) - { - if (btn_down & HidNpadButton_Down) - { - cur_menu->selected = 0; - cur_menu->scroll = 0; - } else { - cur_menu->selected--; - } - } else - if (cur_menu->selected >= limit && cur_menu->elements[cur_menu->selected + 1]) - { - cur_menu->scroll++; - } - } else - if ((btn_down & HidNpadButton_Up) || (btn_held & (HidNpadButton_StickLUp | HidNpadButton_StickRUp))) - { - cur_menu->selected--; - - if (cur_menu->selected == UINT32_MAX) - { - if (btn_down & HidNpadButton_Up) - { - cur_menu->selected = (element_count - 1); - cur_menu->scroll = (element_count > page_size ? (element_count - page_size) : 0); - } else { - cur_menu->selected = 0; - } - } else - if (cur_menu->selected < cur_menu->scroll && cur_menu->scroll > 0) - { - cur_menu->scroll--; - } - } else - if ((btn_down & (HidNpadButton_Right | HidNpadButton_StickLRight | HidNpadButton_StickRRight)) && selected_element_options) - { - selected_element_options->selected++; - if (!selected_element_options->options[selected_element_options->selected]) selected_element_options->selected--; - if (selected_element_options->options_func) selected_element_options->options_func(selected_element_options->selected); - } else - if ((btn_down & (HidNpadButton_Left | HidNpadButton_StickLLeft | HidNpadButton_StickRLeft)) && selected_element_options) - { - selected_element_options->selected--; - if (selected_element_options->selected == UINT32_MAX) selected_element_options->selected = 0; - if (selected_element_options->options_func) selected_element_options->options_func(selected_element_options->selected); - } else - if (btn_down & HidNpadButton_B) - { - if (!cur_menu->parent) break; - - cur_menu = cur_menu->parent; - element_count = menuGetElementCount(cur_menu); - } else - if ((btn_down & HidNpadButton_X) && g_umsDeviceCount) - { - for(u32 i = 0; i < g_umsDeviceCount; i++) usbHsFsUnmountDevice(&(g_umsDevices[i]), false); - - updateStorageList(); - } - - if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp)) svcSleepThread(50000000); // 50 ms - } - -out: - freeStorageList(); - - utilsCloseResources(); - - consoleExit(NULL); - - return ret; -} - -static void utilsScanPads(void) -{ - padUpdate(&g_padState); -} - -static u64 utilsGetButtonsDown(void) -{ - return padGetButtonsDown(&g_padState); -} - -static u64 utilsGetButtonsHeld(void) -{ - return padGetButtons(&g_padState); -} - -static void utilsWaitForButtonPress(u64 flag) -{ - /* Don't consider stick movement as button inputs. */ - if (!flag) flag = ~(HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \ - HidNpadButton_StickRUp | HidNpadButton_StickRDown); - - consoleRefresh(); - - while(appletMainLoop()) - { - utilsScanPads(); - if (utilsGetButtonsDown() & flag) break; - } -} - -static void consolePrint(const char *text, ...) -{ - mutexLock(&g_conMutex); - va_list v; - va_start(v, text); - vfprintf(stdout, text, v); - va_end(v); - mutexUnlock(&g_conMutex); -} - -static void consoleRefresh(void) -{ - mutexLock(&g_conMutex); - fflush(stdout); - consoleUpdate(NULL); - mutexUnlock(&g_conMutex); -} - -static u32 menuGetElementCount(const Menu *menu) -{ - if (!menu || !menu->elements || !menu->elements[0]) return 0; - - u32 cnt; - for(cnt = 0; menu->elements[cnt]; cnt++); - return cnt; -} - -void freeStorageList(void) -{ - u32 elem_count = (2 + g_umsDeviceCount); // sd card, usb host, ums devices - - /* Free all previously allocated data. */ - if (g_storageOptions) - { - for(u32 i = 0; i < elem_count && g_storageOptions[i]; i++) - { - free(g_storageOptions[i]); - g_storageOptions[i] = NULL; - } - - free(g_storageOptions); - g_storageOptions = NULL; - } - - if (g_umsDevices) - { - free(g_umsDevices); - g_umsDevices = NULL; - } - - g_umsDeviceCount = 0; -} - -void updateStorageList(void) -{ - char **tmp = NULL; - u32 elem_count = 0, idx = 0; - - /* Free all previously allocated data. */ - freeStorageList(); - - /* Get UMS devices. */ - g_umsDevices = umsGetDevices(&g_umsDeviceCount); - elem_count = (2 + g_umsDeviceCount); // sd card, usb host, ums devices - - /* Reallocate buffer. */ - tmp = realloc(g_storageOptions, (elem_count + 1) * sizeof(char*)); // NULL terminator - - g_storageOptions = tmp; - tmp = NULL; - - memset(g_storageOptions, 0, (elem_count + 1) * sizeof(char*)); // NULL terminator - - /* Generate UMS device strings. */ - for(u32 i = 0; i < elem_count; i++) - { - u64 total = 0, free = 0; - char total_str[36] = {0}, free_str[32] = {0}; - - g_storageOptions[idx] = calloc(sizeof(char), 0x300); - if (!g_storageOptions[idx]) continue; - - if (i == 1) - { - sprintf(g_storageOptions[idx], "usb host (pc)"); - } else { - UsbHsFsDevice *ums_device = (i >= 2 ? &(g_umsDevices[i - 2]) : NULL); - - sprintf(total_str, "%s/", i == 0 ? DEVOPTAB_SDMC_DEVICE : ums_device->name); - utilsGetFileSystemStatsByPath(total_str, &total, &free); - utilsGenerateFormattedSizeString(total, total_str, sizeof(total_str)); - utilsGenerateFormattedSizeString(free, free_str, sizeof(free_str)); - - if (i == 0) - { - sprintf(g_storageOptions[idx], DEVOPTAB_SDMC_DEVICE " (%s / %s)", free_str, total_str); - } else { - if (ums_device->product_name[0]) - { - sprintf(g_storageOptions[idx], "%s (%s, LUN %u, FS #%u, %s)", ums_device->name, ums_device->product_name, ums_device->lun, ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(ums_device->fs_type)); - } else { - sprintf(g_storageOptions[idx], "%s (LUN %u, FS #%u, %s)", ums_device->name, ums_device->lun, ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(ums_device->fs_type)); - } - - sprintf(g_storageOptions[idx] + strlen(g_storageOptions[idx]), " (%s / %s)", free_str, total_str); - } - } - - idx++; - } - - /* Update storage menu element options. */ - if (g_storageMenuElementOption.selected >= elem_count) g_storageMenuElementOption.selected = 0; - g_storageMenuElementOption.options = g_storageOptions; -} - -NX_INLINE bool useUsbHost(void) -{ - return (g_storageMenuElementOption.selected == 1); -} - -static bool waitForGameCard(void) -{ - consoleClear(); - consolePrint("waiting for gamecard...\n"); - consoleRefresh(); - - u8 status = GameCardStatus_NotInserted; - - while((g_appletStatus = appletMainLoop())) - { - status = gamecardGetStatus(); - if (status > GameCardStatus_Processing) break; - } - - if (!g_appletStatus) return false; - - switch(status) - { - case GameCardStatus_NoGameCardPatchEnabled: - consolePrint("\"nogc\" patch enabled, please disable it and reboot your console\n"); - break; - case GameCardStatus_LotusAsicFirmwareUpdateRequired: - consolePrint("gamecard controller firmware update required, please update your console\n"); - break; - case GameCardStatus_InsertedAndInfoNotLoaded: - consolePrint("unexpected I/O error occurred, please check the logfile\n"); - break; - default: - break; - } - - if (status != GameCardStatus_InsertedAndInfoLoaded) - { - consolePrint("press any button\n"); - utilsWaitForButtonPress(0); - return false; - } - - return true; -} - -static bool waitForUsb(void) -{ - if (usbIsReady()) return true; - - consolePrint("waiting for usb session...\n"); - consoleRefresh(); - - while((g_appletStatus = appletMainLoop())) - { - if (usbIsReady()) break; - } - - return g_appletStatus; -} - -static void generateDumpTxt(void) -{ - *txt_info = '\0'; - - struct tm ts = {0}; - time_t now = time(NULL); - - /* Get UTC time. */ - gmtime_r(&now, &ts); - ts.tm_year += 1900; - ts.tm_mon++; - - /* Generate dump text. */ - snprintf(txt_info, MAX_ELEMENTS(txt_info), "tool: nxdumptool\r\n" \ - "version: " APP_VERSION "\r\n" \ - "branch: " GIT_BRANCH "\r\n" \ - "commit: " GIT_COMMIT "\r\n" \ - "build date: " BUILD_TIMESTAMP "\r\n" \ - "dump date: %d-%02d-%02d %02d:%02d:%02d UTC\r\n", \ - ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec); -} - -static bool saveFileData(const char *filepath, void *data, size_t data_size) -{ - if (!filepath || !*filepath || !data || !data_size) - { - consolePrint("invalid parameters to save file data!\n"); - return false; - } - - if (useUsbHost()) - { - if (!usbSendFilePropertiesCommon(data_size, filepath)) - { - consolePrint("failed to send file properties for \"%s\"!\n", filepath); - return false; - } - - if (!usbSendFileData(data, data_size)) - { - consolePrint("failed to send file data for \"%s\"!\n", filepath); - return false; - } - } else { - FILE *fp = fopen(filepath, "wb"); - if (!fp) - { - consolePrint("failed to open \"%s\" for writing!\n", filepath); - return false; - } - - size_t ret = fwrite(data, 1, data_size, fp); - fclose(fp); - - if (ret != data_size) - { - consolePrint("failed to write 0x%lX byte(s) to \"%s\"! (%d)\n", data_size, filepath, errno); - remove(filepath); - } - } - - return true; -} - -static bool saveDumpTxt(void) -{ - if (!*path || !*txt_info) return true; - - path[strlen(path) - 3] = '\0'; - strcat(path, "txt"); - - return saveFileData(path, txt_info, strlen(txt_info)); -} - -static char *generateOutputFileName(const char *extension) -{ - char *filename = NULL, *prefix = NULL, *output = NULL; - u32 dev_idx = g_storageMenuElementOption.selected; - - if (!extension || !*extension || !(filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, dev_idx > 0 ? TitleFileNameIllegalCharReplaceType_IllegalFsChars : TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly))) - { - consolePrint("failed to get gamecard filename!\n"); - return NULL; - } - - if (!useUsbHost()) - { - prefix = calloc(sizeof(char), 0x300); - if (!prefix) - { - consolePrint("failed to generate prefix!\n"); - free(filename); - return NULL; - } - - sprintf(prefix, "%s/gamecard_data/", dev_idx == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[dev_idx - 2].name); - } - - output = utilsGeneratePath(prefix, filename, extension); - - if (prefix) free(prefix); - free(filename); - - if (output) - { - snprintf(path, MAX_ELEMENTS(path), "%s", output); - utilsCreateDirectoryTree(path, false); - } else { - consolePrint("failed to generate output filename!\n"); - } - - return output; -} - -static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out) -{ - if (!out) - { - consolePrint("invalid parameters to dump gamecard security information!\n"); - return false; - } - - if (!gamecardGetSecurityInformation(out)) - { - consolePrint("failed to get gamecard security information\n"); - return false; - } - - consolePrint("get gamecard security information ok\n"); - return true; -} - -static bool saveGameCardSpecificData(void) -{ - GameCardSecurityInformation gc_security_information = {0}; - bool success = false; - u32 crc = 0; - char *filename = NULL; - - if (!dumpGameCardSecurityInformation(&gc_security_information)) goto end; - - crc = crc32Calculate(&(gc_security_information.specific_data), sizeof(GameCardSpecificData)); - snprintf(path, MAX_ELEMENTS(path), " (Specific Data) (%08X).bin", crc); - - filename = generateOutputFileName(path); - if (!filename) goto end; - - if (!saveFileData(filename, &(gc_security_information.specific_data), sizeof(GameCardSpecificData))) goto end; - - consolePrint("successfully saved specific data as \"%s\"\n", filename); - success = true; - -end: - if (filename) free(filename); - - return success; -} - -static bool saveGameCardCertificate(void) -{ - FsGameCardCertificate gc_cert = {0}; - bool success = false; - u32 crc = 0; - char *filename = NULL; - - if (!gamecardGetCertificate(&gc_cert)) - { - consolePrint("failed to get gamecard certificate\n"); - goto end; - } - - consolePrint("get gamecard certificate ok\n"); - - crc = crc32Calculate(&gc_cert, sizeof(FsGameCardCertificate)); - snprintf(path, MAX_ELEMENTS(path), " (Certificate) (%08X).bin", crc); - - filename = generateOutputFileName(path); - if (!filename) goto end; - - if (!saveFileData(filename, &gc_cert, sizeof(FsGameCardCertificate))) goto end; - - consolePrint("successfully saved certificate as \"%s\"\n", filename); - success = true; - -end: - if (filename) free(filename); - - return success; -} - -static bool saveGameCardInitialData(void) -{ - GameCardSecurityInformation gc_security_information = {0}; - bool success = false; - u32 crc = 0; - char *filename = NULL; - - if (!dumpGameCardSecurityInformation(&gc_security_information)) goto end; - - crc = crc32Calculate(&(gc_security_information.initial_data), sizeof(GameCardInitialData)); - snprintf(path, MAX_ELEMENTS(path), " (Initial Data) (%08X).bin", crc); - - filename = generateOutputFileName(path); - if (!filename) goto end; - - if (!saveFileData(filename, &(gc_security_information.initial_data), sizeof(GameCardInitialData))) goto end; - - consolePrint("successfully saved initial data as \"%s\"\n", filename); - success = true; - -end: - if (filename) free(filename); - - return success; -} - -static bool saveGameCardIdSet(void) -{ - FsGameCardIdSet id_set = {0}; - bool success = false; - u32 crc = 0; - char *filename = NULL; - - if (!gamecardGetIdSet(&id_set)) goto end; - - crc = crc32Calculate(&id_set, sizeof(FsGameCardIdSet)); - snprintf(path, MAX_ELEMENTS(path), " (Card ID Set) (%08X).bin", crc); - - filename = generateOutputFileName(path); - if (!filename) goto end; - - if (!saveFileData(filename, &id_set, sizeof(FsGameCardIdSet))) goto end; - - consolePrint("successfully saved gamecard id set as \"%s\"\n", filename); - success = true; - -end: - if (filename) free(filename); - - return success; -} - -static bool saveGameCardImage(void) -{ - u64 gc_size = 0, free_space = 0; - - u32 key_area_crc = 0; - GameCardKeyArea gc_key_area = {0}; - GameCardSecurityInformation gc_security_information = {0}; - - ThreadSharedData shared_data = {0}; - Thread read_thread = {0}, write_thread = {0}; - - char *filename = NULL; - u32 dev_idx = g_storageMenuElementOption.selected; - - bool success = false; - - consolePrint("gamecard image dump\nappend key area: %s | keep certificate: %s | trim dump: %s | calculate crc32: %s\n\n", g_appendKeyArea ? "yes" : "no", g_keepCertificate ? "yes" : "no", g_trimDump ? "yes" : "no", g_calcCrc ? "yes" : "no"); - - shared_data.data = usbAllocatePageAlignedBuffer(BLOCK_SIZE); - if (!shared_data.data) - { - consolePrint("failed to allocate memory for the dump procedure!\n"); - goto end; - } - - if ((!g_trimDump && !gamecardGetTotalSize(&gc_size)) || (g_trimDump && !gamecardGetTrimmedSize(&gc_size)) || !gc_size) - { - consolePrint("failed to get gamecard size!\n"); - goto end; - } - - shared_data.total_size = gc_size; - - consolePrint("gamecard size: 0x%lX\n", gc_size); - - if (g_appendKeyArea) - { - gc_size += sizeof(GameCardKeyArea); - - if (!dumpGameCardSecurityInformation(&gc_security_information)) goto end; - - memcpy(&(gc_key_area.initial_data), &(gc_security_information.initial_data), sizeof(GameCardInitialData)); - - if (g_calcCrc) - { - key_area_crc = crc32Calculate(&gc_key_area, sizeof(GameCardKeyArea)); - shared_data.full_xci_crc = key_area_crc; - } - - consolePrint("gamecard size (with key area): 0x%lX\n", gc_size); - } - - snprintf(path, MAX_ELEMENTS(path), " (%s) (%s) (%s).xci", g_appendKeyArea ? "keyarea" : "keyarealess", g_keepCertificate ? "cert" : "certless", g_trimDump ? "trimmed" : "untrimmed"); - filename = generateOutputFileName(path); - if (!filename) goto end; - - if (useUsbHost()) - { - if (!usbSendFilePropertiesCommon(gc_size, filename)) - { - consolePrint("failed to send file properties for \"%s\"!\n", filename); - goto end; - } - - if (g_appendKeyArea && !usbSendFileData(&gc_key_area, sizeof(GameCardKeyArea))) - { - consolePrint("failed to send gamecard key area data!\n"); - goto end; - } - } else { - if (!utilsGetFileSystemStatsByPath(filename, NULL, &free_space)) - { - consolePrint("failed to retrieve free space from selected device\n"); - goto end; - } - - if (gc_size >= free_space) - { - consolePrint("dump size exceeds free space\n"); - goto end; - } - - if (dev_idx == 0) - { - if (gc_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 && gc_size > FAT32_FILESIZE_LIMIT) - { - consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n"); - goto end; - } - } - - shared_data.fp = fopen(filename, "wb"); - if (!shared_data.fp) - { - consolePrint("failed to open \"%s\" for writing!\n", filename); - goto end; - } - - if (g_appendKeyArea && fwrite(&gc_key_area, 1, sizeof(GameCardKeyArea), shared_data.fp) != sizeof(GameCardKeyArea)) - { - consolePrint("failed to write gamecard key area data!\n"); - goto end; - } - } - - consolePrint("creating threads\n"); - utilsCreateThread(&read_thread, read_thread_func, &shared_data, 2); - utilsCreateThread(&write_thread, write_thread_func, &shared_data, 2); - - u8 prev_time = 0; - u64 prev_size = 0; - u8 percent = 0; - - time_t start = 0, btn_cancel_start_tmr = 0, btn_cancel_end_tmr = 0; - bool btn_cancel_cur_state = false, btn_cancel_prev_state = false; - - consolePrint("hold b to cancel\n\n"); - consoleRefresh(); - - start = time(NULL); - - while(shared_data.data_written < shared_data.total_size) - { - if (shared_data.read_error || shared_data.write_error) break; - - struct tm ts = {0}; - time_t now = time(NULL); - localtime_r(&now, &ts); - - size_t size = shared_data.data_written; - - utilsScanPads(); - btn_cancel_cur_state = (utilsGetButtonsHeld() & HidNpadButton_B); - - if (btn_cancel_cur_state && btn_cancel_cur_state != btn_cancel_prev_state) - { - btn_cancel_start_tmr = now; - } else - if (btn_cancel_cur_state && btn_cancel_cur_state == btn_cancel_prev_state) - { - btn_cancel_end_tmr = now; - if ((btn_cancel_end_tmr - btn_cancel_start_tmr) >= 3) - { - mutexLock(&g_fileMutex); - shared_data.transfer_cancelled = true; - mutexUnlock(&g_fileMutex); - break; - } - } else { - btn_cancel_start_tmr = btn_cancel_end_tmr = 0; - } - - btn_cancel_prev_state = btn_cancel_cur_state; - - if (prev_time == ts.tm_sec || prev_size == size) continue; - - percent = (u8)((size * 100) / shared_data.total_size); - - prev_time = ts.tm_sec; - prev_size = size; - - consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_data.total_size, percent, (now - start)); - consoleRefresh(); - } - - start = (time(NULL) - start); - - consolePrint("\nwaiting for threads to join\n"); - utilsJoinThread(&read_thread); - consolePrint("read_thread done: %lu\n", time(NULL)); - utilsJoinThread(&write_thread); - consolePrint("write_thread done: %lu\n", time(NULL)); - - if (shared_data.read_error || shared_data.write_error) - { - consolePrint("i/o error\n"); - goto end; - } - - if (shared_data.transfer_cancelled) - { - consolePrint("process cancelled\n"); - goto end; - } - - consolePrint("process completed in %lu seconds\n", start); - success = true; - - if (g_calcCrc) - { - if (g_appendKeyArea) consolePrint("key area crc: %08X | ", key_area_crc); - consolePrint("xci crc: %08X", shared_data.xci_crc); - if (g_appendKeyArea) consolePrint(" | xci crc (with key area): %08X", shared_data.full_xci_crc); - consolePrint("\n"); - } - -end: - consoleRefresh(); - - if (shared_data.fp) - { - fclose(shared_data.fp); - shared_data.fp = NULL; - } - - if (!success && !useUsbHost()) utilsRemoveConcatenationFile(filename); - - if (shared_data.data) free(shared_data.data); - - if (filename) free(filename); - - return success; -} - -static bool saveConsoleLafwBlob(void) -{ - u64 lafw_version = 0; - LotusAsicFirmwareBlob lafw_blob = {0}; - bool success = false; - u32 crc = 0, dev_idx = g_storageMenuElementOption.selected; - - if (!gamecardGetLotusAsicFirmwareBlob(&lafw_blob, &lafw_version)) - { - consolePrint("failed to get console lafw blob\n"); - goto end; - } - - const char *fw_type_str = gamecardGetLafwTypeString(lafw_blob.fw_type); - if (!fw_type_str) fw_type_str = "Unknown"; - - const char *dev_type_str = gamecardGetLafwDeviceTypeString(lafw_blob.device_type); - if (!dev_type_str) dev_type_str = "Unknown"; - - consolePrint("get console lafw blob ok\n"); - - crc = crc32Calculate(&lafw_blob, sizeof(LotusAsicFirmwareBlob)); - snprintf(path, MAX_ELEMENTS(path), "%s/gamecard_data/LAFW (%s) (%s) (v%lu) (%08X).bin", dev_idx == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[dev_idx - 2].name, fw_type_str, dev_type_str, lafw_version, crc); - utilsCreateDirectoryTree(path, false); - - if (!saveFileData(path, &lafw_blob, sizeof(LotusAsicFirmwareBlob))) goto end; - - consolePrint("successfully saved lafw blob as \"%s\"\n", path); - success = true; - -end: - return success; -} - -static void changeKeyAreaOption(u32 idx) -{ - g_appendKeyArea = (idx > 0); -} - -static void changeCertificateOption(u32 idx) -{ - g_keepCertificate = (idx > 0); -} - -static void changeTrimOption(u32 idx) -{ - g_trimDump = (idx > 0); -} - -static void changeCrcOption(u32 idx) -{ - g_calcCrc = (idx > 0); -} - -static void read_thread_func(void *arg) -{ - ThreadSharedData *shared_data = (ThreadSharedData*)arg; - if (!shared_data || !shared_data->data || !shared_data->total_size) - { - shared_data->read_error = true; - goto end; - } - - u8 *buf = malloc(BLOCK_SIZE); - if (!buf) - { - shared_data->read_error = true; - goto end; - } - - for(u64 offset = 0, blksize = BLOCK_SIZE; offset < shared_data->total_size; offset += blksize) - { - if (blksize > (shared_data->total_size - offset)) blksize = (shared_data->total_size - offset); - - /* Check if the transfer has been cancelled by the user */ - if (shared_data->transfer_cancelled) - { - condvarWakeAll(&g_writeCondvar); - break; - } - - /* Read current data chunk */ - shared_data->read_error = !gamecardReadStorage(buf, blksize, offset); - if (shared_data->read_error) - { - condvarWakeAll(&g_writeCondvar); - break; - } - - /* Remove certificate */ - if (!g_keepCertificate && offset == 0) memset(buf + GAMECARD_CERTIFICATE_OFFSET, 0xFF, sizeof(FsGameCardCertificate)); - - /* Update checksum */ - if (g_calcCrc) - { - shared_data->xci_crc = crc32CalculateWithSeed(shared_data->xci_crc, buf, blksize); - if (g_appendKeyArea) shared_data->full_xci_crc = crc32CalculateWithSeed(shared_data->full_xci_crc, buf, blksize); - } - - /* Wait until the previous data chunk has been written */ - mutexLock(&g_fileMutex); - - if (shared_data->data_size && !shared_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex); - - if (shared_data->write_error) - { - mutexUnlock(&g_fileMutex); - break; - } - - /* Copy current file data chunk to the shared buffer */ - memcpy(shared_data->data, buf, blksize); - shared_data->data_size = blksize; - - /* Wake up the write thread to continue writing data */ - mutexUnlock(&g_fileMutex); - condvarWakeAll(&g_writeCondvar); - } - - free(buf); - -end: - threadExit(); -} - -static void write_thread_func(void *arg) -{ - ThreadSharedData *shared_data = (ThreadSharedData*)arg; - if (!shared_data || !shared_data->data) - { - shared_data->write_error = true; - goto end; - } - - while(shared_data->data_written < shared_data->total_size) - { - /* Wait until the current file data chunk has been read */ - mutexLock(&g_fileMutex); - - if (!shared_data->data_size && !shared_data->read_error) condvarWait(&g_writeCondvar, &g_fileMutex); - - if (shared_data->read_error || shared_data->transfer_cancelled) - { - if (shared_data->transfer_cancelled && useUsbHost()) usbCancelFileTransfer(); - mutexUnlock(&g_fileMutex); - break; - } - - /* Write current file data chunk */ - if (useUsbHost()) - { - shared_data->write_error = !usbSendFileData(shared_data->data, shared_data->data_size); - } else { - shared_data->write_error = (fwrite(shared_data->data, 1, shared_data->data_size, shared_data->fp) != shared_data->data_size); - } - - if (!shared_data->write_error) - { - shared_data->data_written += shared_data->data_size; - shared_data->data_size = 0; - } - - /* Wake up the read thread to continue reading data */ - mutexUnlock(&g_fileMutex); - condvarWakeAll(&g_readCondvar); - - if (shared_data->write_error) break; - } - -end: - threadExit(); -} diff --git a/code_templates/nsp_dumper.c b/code_templates/nsp_dumper.c deleted file mode 100644 index ec827f8..0000000 --- a/code_templates/nsp_dumper.c +++ /dev/null @@ -1,1490 +0,0 @@ -/* - * main.c - * - * Copyright (c) 2020-2023, DarkMatterCore . - * - * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). - * - * nxdumptool is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * nxdumptool is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "nxdt_utils.h" -#include "gamecard.h" -#include "title.h" -#include "cnmt.h" -#include "program_info.h" -#include "nacp.h" -#include "legal_info.h" -#include "cert.h" -#include "usb.h" - -#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE -#define OUTPATH "/nsp/" - -bool g_borealisInitialized = false; - -static PadState g_padState = {0}; - -typedef struct -{ - void *data; - size_t data_written; - size_t total_size; - bool error; - bool transfer_cancelled; -} ThreadSharedData; - -static const char *dump_type_strings[] = { - "dump base application", - "dump update", - "dump dlc", - "dump dlc update" -}; - -static const u32 dump_type_strings_count = MAX_ELEMENTS(dump_type_strings); - -typedef struct { - char str[64]; - u32 val; -} options_t; - -static options_t options[] = { - { "set download distribution type", 0 }, - { "remove console specific data", 1 }, - { "remove titlekey crypto (overrides previous option)", 0 }, - { "disable linked account requirement", 0 }, - { "enable screenshots", 0 }, - { "enable video capture", 0 }, - { "disable hdcp", 0 }, - { "append authoringtool data", 1 }, - { "output device", 0 } -}; - -static const u32 options_count = MAX_ELEMENTS(options); - -static Mutex g_conMutex = 0; - -static UsbHsFsDevice *ums_devices = NULL; -static u32 ums_device_count = 0; - -static void utilsScanPads(void) -{ - padUpdate(&g_padState); -} - -static u64 utilsGetButtonsDown(void) -{ - return padGetButtonsDown(&g_padState); -} - -static u64 utilsGetButtonsHeld(void) -{ - return padGetButtons(&g_padState); -} - -static void utilsWaitForButtonPress(u64 flag) -{ - /* Don't consider stick movement as button inputs. */ - if (!flag) flag = ~(HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \ - HidNpadButton_StickRUp | HidNpadButton_StickRDown); - - while(appletMainLoop()) - { - utilsScanPads(); - if (utilsGetButtonsDown() & flag) break; - } -} - -static void consolePrint(const char *text, ...) -{ - mutexLock(&g_conMutex); - va_list v; - va_start(v, text); - vfprintf(stdout, text, v); - va_end(v); - mutexUnlock(&g_conMutex); -} - -static void consoleRefresh(void) -{ - mutexLock(&g_conMutex); - fflush(stdout); - consoleUpdate(NULL); - mutexUnlock(&g_conMutex); -} - -static void dump_thread_func(void *arg) -{ - ThreadSharedData *shared_data = (ThreadSharedData*)arg; - - TitleInfo *title_info = NULL; - - bool set_download_type = (options[0].val == 1); - bool remove_console_data = (options[1].val == 1); - bool remove_titlekey_crypto = (options[2].val == 1); - bool patch_sua = (options[3].val == 1); - bool patch_screenshot = (options[4].val == 1); - bool patch_video_capture = (options[5].val == 1); - bool patch_hdcp = (options[6].val == 1); - bool append_authoringtool_data = (options[7].val == 1); - u32 output_device = options[8].val; - bool success = false; - - UsbHsFsDevice *ums_device = (output_device < 2 ? NULL : &(ums_devices[output_device - 2])); - - u64 free_space = 0; - - u8 *buf = NULL; - char *dump_name = NULL, *path = NULL; - FILE *fd = NULL; - - NcaContext *nca_ctx = NULL; - - NcaContext *meta_nca_ctx = NULL; - ContentMetaContext cnmt_ctx = {0}; - - ProgramInfoContext *program_info_ctx = NULL; - u32 program_idx = 0, program_count = 0; - - NacpContext *nacp_ctx = NULL; - u32 control_idx = 0, control_count = 0; - - LegalInfoContext *legal_info_ctx = NULL; - u32 legal_info_idx = 0, legal_info_count = 0; - - Ticket tik = {0}; - TikCommonBlock *tik_common_block = NULL; - - u8 *raw_cert_chain = NULL; - u64 raw_cert_chain_size = 0; - - PartitionFileSystemFileContext pfs_file_ctx = {0}; - pfsInitializeFileContext(&pfs_file_ctx); - - char entry_name[64] = {0}; - u64 nsp_header_size = 0, nsp_size = 0, nsp_offset = 0; - char *tmp_name = NULL; - - Sha256Context sha256_ctx = {0}; - u8 sha256_hash[SHA256_HASH_SIZE] = {0}; - - if (!shared_data || !(title_info = (TitleInfo*)shared_data->data) || !title_info->content_count || !title_info->content_infos) goto end; - - if (ums_device && ums_device->write_protect) - { - consolePrint("device \"%s\" has write protection enabled!\n", ums_device->name); - goto end; - } - - /* Allocate memory for the dump process. */ - if (!(buf = usbAllocatePageAlignedBuffer(BLOCK_SIZE))) - { - consolePrint("buf alloc failed\n"); - goto end; - } - - /* Generate output path. */ - if (!(dump_name = titleGenerateFileName(title_info, TitleNamingConvention_Full, output_device == 0 ? TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly : TitleFileNameIllegalCharReplaceType_IllegalFsChars))) - { - consolePrint("title generate file name failed\n"); - goto end; - } - - if (output_device != 1) sprintf(entry_name, "%s" OUTPATH, ums_device ? ums_device->name : DEVOPTAB_SDMC_DEVICE); - - if (!(path = utilsGeneratePath(entry_name, dump_name, ".nsp"))) - { - consolePrint("generate path failed\n"); - goto end; - } - - // get device free space - if (output_device != 1 && !utilsGetFileSystemStatsByPath(path, NULL, &free_space)) - { - consolePrint("failed to retrieve free space from selected device\n"); - goto end; - } - - if (!(nca_ctx = calloc(title_info->content_count, sizeof(NcaContext)))) - { - consolePrint("nca ctx calloc failed\n"); - goto end; - } - - // determine if we should initialize programinfo ctx - if (append_authoringtool_data) - { - program_count = titleGetContentCountByType(title_info, NcmContentType_Program); - if (program_count && !(program_info_ctx = calloc(program_count, sizeof(ProgramInfoContext)))) - { - consolePrint("program info ctx calloc failed\n"); - goto end; - } - } - - // determine if we should initialize nacp ctx - if (patch_sua || patch_screenshot || patch_video_capture || patch_hdcp || append_authoringtool_data) - { - control_count = titleGetContentCountByType(title_info, NcmContentType_Control); - if (control_count && !(nacp_ctx = calloc(control_count, sizeof(NacpContext)))) - { - consolePrint("nacp ctx calloc failed\n"); - goto end; - } - } - - // determine if we should initialize legalinfo ctx - if (append_authoringtool_data) - { - legal_info_count = titleGetContentCountByType(title_info, NcmContentType_LegalInformation); - if (legal_info_count && !(legal_info_ctx = calloc(legal_info_count, sizeof(LegalInfoContext)))) - { - consolePrint("legal info ctx calloc failed\n"); - goto end; - } - } - - // set meta nca as the last nca - meta_nca_ctx = &(nca_ctx[title_info->content_count - 1]); - - if (!ncaInitializeContext(meta_nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \ - titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Meta, 0), title_info->version.value, &tik)) - { - consolePrint("Meta nca initialize ctx failed\n"); - goto end; - } - - consolePrint("Meta nca initialize ctx succeeded\n"); - - if (!cnmtInitializeContext(&cnmt_ctx, meta_nca_ctx)) - { - consolePrint("cnmt initialize ctx failed\n"); - goto end; - } - - consolePrint("cnmt initialize ctx succeeded (%s)\n", meta_nca_ctx->content_id_str); - - // initialize nca context - // initialize content type context - // generate nca patches (if needed) - // generate content type xml - for(u32 i = 0, j = 0; i < title_info->content_count; i++) - { - // skip meta nca since we already initialized it - NcmContentInfo *content_info = &(title_info->content_infos[i]); - if (content_info->content_type == NcmContentType_Meta) continue; - - NcaContext *cur_nca_ctx = &(nca_ctx[j]); - if (!ncaInitializeContext(cur_nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), content_info, title_info->version.value, &tik)) - { - consolePrint("%s #%u initialize nca ctx failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset); - goto end; - } - - consolePrint("%s #%u initialize nca ctx succeeded\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset); - - // don't go any further with this nca if we can't access its fs data because it's pointless - // TODO: add preload warning - if (cur_nca_ctx->rights_id_available && !cur_nca_ctx->titlekey_retrieved) - { - j++; - continue; - } - - // set download distribution type - // has no effect if this nca uses NcaDistributionType_Download - if (set_download_type) ncaSetDownloadDistributionType(cur_nca_ctx); - - // remove titlekey crypto - // has no effect if this nca doesn't use titlekey crypto - if (remove_titlekey_crypto && !ncaRemoveTitleKeyCrypto(cur_nca_ctx)) - { - consolePrint("nca remove titlekey crypto failed\n"); - goto end; - } - - if (!cur_nca_ctx->fs_ctx[0].has_sparse_layer) - { - switch(content_info->content_type) - { - case NcmContentType_Program: - { - // don't proceed if we didn't allocate programinfo ctx or if we're dealing with a sparse layer - if (!program_count || !program_info_ctx) break; - - ProgramInfoContext *cur_program_info_ctx = &(program_info_ctx[program_idx]); - - if (!programInfoInitializeContext(cur_program_info_ctx, cur_nca_ctx)) - { - consolePrint("initialize program info ctx failed (%s)\n", cur_nca_ctx->content_id_str); - goto end; - } - - if (!programInfoGenerateAuthoringToolXml(cur_program_info_ctx)) - { - consolePrint("program info xml failed (%s)\n", cur_nca_ctx->content_id_str); - goto end; - } - - program_idx++; - - consolePrint("initialize program info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str); - - break; - } - case NcmContentType_Control: - { - // don't proceed if we didn't allocate nacp ctx - if (!control_count || !nacp_ctx) break; - - NacpContext *cur_nacp_ctx = &(nacp_ctx[control_idx]); - - if (!nacpInitializeContext(cur_nacp_ctx, cur_nca_ctx)) - { - consolePrint("initialize nacp ctx failed (%s)\n", cur_nca_ctx->content_id_str); - goto end; - } - - if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture, patch_hdcp)) - { - consolePrint("nacp nca patch failed (%s)\n", cur_nca_ctx->content_id_str); - goto end; - } - - if (append_authoringtool_data && !nacpGenerateAuthoringToolXml(cur_nacp_ctx, title_info->version.value, cnmtGetRequiredTitleVersion(&cnmt_ctx))) - { - consolePrint("nacp xml failed (%s)\n", cur_nca_ctx->content_id_str); - goto end; - } - - control_idx++; - - consolePrint("initialize nacp ctx succeeded (%s)\n", cur_nca_ctx->content_id_str); - - break; - } - case NcmContentType_LegalInformation: - { - // don't proceed if we didn't allocate legalinfo ctx - if (!legal_info_count || !legal_info_ctx) break; - - LegalInfoContext *cur_legal_info_ctx = &(legal_info_ctx[legal_info_idx]); - - if (!legalInfoInitializeContext(cur_legal_info_ctx, cur_nca_ctx)) - { - consolePrint("initialize legal info ctx failed (%s)\n", cur_nca_ctx->content_id_str); - goto end; - } - - legal_info_idx++; - - consolePrint("initialize legal info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str); - - break; - } - default: - break; - } - } - - if (!ncaEncryptHeader(cur_nca_ctx)) - { - consolePrint("%s #%u encrypt nca header failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset); - goto end; - } - - j++; - } - - consoleRefresh(); - - // generate cnmt xml right away even though we don't yet have all the data we need - // This is because we need its size to calculate the full nsp size - if (append_authoringtool_data && !cnmtGenerateAuthoringToolXml(&cnmt_ctx, nca_ctx, title_info->content_count)) - { - consolePrint("cnmt xml #1 failed\n"); - goto end; - } - - bool retrieve_tik_cert = (!remove_titlekey_crypto && tik.size > 0); - if (retrieve_tik_cert) - { - if (!(tik_common_block = tikGetCommonBlock(tik.data))) - { - consolePrint("tik common block failed"); - goto end; - } - - if (remove_console_data && tik_common_block->titlekey_type == TikTitleKeyType_Personalized) - { - if (!tikConvertPersonalizedTicketToCommonTicket(&tik, &raw_cert_chain, &raw_cert_chain_size)) - { - consolePrint("tik convert failed\n"); - goto end; - } - } else { - raw_cert_chain = (title_info->storage_id == NcmStorageId_GameCard ? certRetrieveRawCertificateChainFromGameCardByRightsId(&(tik_common_block->rights_id), &raw_cert_chain_size) : \ - certGenerateRawCertificateChainBySignatureIssuer(tik_common_block->issuer, &raw_cert_chain_size)); - if (!raw_cert_chain) - { - consolePrint("cert failed\n"); - goto end; - } - } - } - - // add nca info - for(u32 i = 0; i < title_info->content_count; i++) - { - NcaContext *cur_nca_ctx = &(nca_ctx[i]); - sprintf(entry_name, "%s.%s", cur_nca_ctx->content_id_str, cur_nca_ctx->content_type == NcmContentType_Meta ? "cnmt.nca" : "nca"); - - if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_nca_ctx->content_size, NULL)) - { - consolePrint("pfs add entry failed: %s\n", entry_name); - goto end; - } - } - - // add cnmt xml info - if (append_authoringtool_data) - { - sprintf(entry_name, "%s.cnmt.xml", meta_nca_ctx->content_id_str); - if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cnmt_ctx.authoring_tool_xml_size, &(meta_nca_ctx->content_type_ctx_data_idx))) - { - consolePrint("pfs add entry failed: %s\n", entry_name); - goto end; - } - } - - // add content type ctx data info - u32 limit = append_authoringtool_data ? (title_info->content_count - 1) : 0; - for(u32 i = 0; i < limit; i++) - { - bool ret = false; - NcaContext *cur_nca_ctx = &(nca_ctx[i]); - if (!cur_nca_ctx->content_type_ctx) continue; - - switch(cur_nca_ctx->content_type) - { - case NcmContentType_Program: - { - ProgramInfoContext *cur_program_info_ctx = (ProgramInfoContext*)cur_nca_ctx->content_type_ctx; - sprintf(entry_name, "%s.programinfo.xml", cur_nca_ctx->content_id_str); - ret = pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_program_info_ctx->authoring_tool_xml_size, &(cur_nca_ctx->content_type_ctx_data_idx)); - break; - } - case NcmContentType_Control: - { - NacpContext *cur_nacp_ctx = (NacpContext*)cur_nca_ctx->content_type_ctx; - - for(u8 j = 0; j < cur_nacp_ctx->icon_count; j++) - { - NacpIconContext *icon_ctx = &(cur_nacp_ctx->icon_ctx[j]); - sprintf(entry_name, "%s.nx.%s.jpg", cur_nca_ctx->content_id_str, nacpGetLanguageString(icon_ctx->language)); - if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, icon_ctx->icon_size, j == 0 ? &(cur_nca_ctx->content_type_ctx_data_idx) : NULL)) - { - consolePrint("pfs add entry failed: %s\n", entry_name); - goto end; - } - } - - sprintf(entry_name, "%s.nacp.xml", cur_nca_ctx->content_id_str); - ret = pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_nacp_ctx->authoring_tool_xml_size, !cur_nacp_ctx->icon_count ? &(cur_nca_ctx->content_type_ctx_data_idx) : NULL); - break; - } - case NcmContentType_LegalInformation: - { - LegalInfoContext *cur_legal_info_ctx = (LegalInfoContext*)cur_nca_ctx->content_type_ctx; - sprintf(entry_name, "%s.legalinfo.xml", cur_nca_ctx->content_id_str); - ret = pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_legal_info_ctx->authoring_tool_xml_size, &(cur_nca_ctx->content_type_ctx_data_idx)); - break; - } - default: - break; - } - - if (!ret) - { - consolePrint("pfs add entry failed: %s\n", entry_name); - goto end; - } - } - - // add ticket and cert info - if (retrieve_tik_cert) - { - sprintf(entry_name, "%s.tik", tik.rights_id_str); - if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, tik.size, NULL)) - { - consolePrint("pfs add entry failed: %s\n", entry_name); - goto end; - } - - sprintf(entry_name, "%s.cert", tik.rights_id_str); - if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, raw_cert_chain_size, NULL)) - { - consolePrint("pfs add entry failed: %s\n", entry_name); - goto end; - } - } - - // write buffer to memory buffer - if (!pfsWriteFileContextHeaderToMemoryBuffer(&pfs_file_ctx, buf, BLOCK_SIZE, &nsp_header_size)) - { - consolePrint("pfs write header to mem #1 failed\n"); - goto end; - } - - nsp_size = (nsp_header_size + pfs_file_ctx.fs_size); - consolePrint("nsp header size: 0x%lX | nsp size: 0x%lX\n", nsp_header_size, nsp_size); - consoleRefresh(); - - if (output_device != 1) - { - if (nsp_size >= free_space) - { - consolePrint("nsp size exceeds free space\n"); - goto end; - } - - if (ums_device && ums_device->fs_type < UsbHsFsDeviceFileSystemType_exFAT && nsp_size > FAT32_FILESIZE_LIMIT) - { - consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n"); - goto end; - } - - utilsCreateDirectoryTree(path, false); - - if (!ums_device && nsp_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(path)) - { - consolePrint("create concatenation file failed\n"); - goto end; - } - - if (!(fd = fopen(path, "wb"))) - { - consolePrint("fopen failed\n"); - goto end; - } - - // write placeholder header - memset(buf, 0, nsp_header_size); - fwrite(buf, 1, nsp_header_size, fd); - } else { - if (!usbSendFileProperties(nsp_size, path, (u32)nsp_header_size)) - { - consolePrint("usb send file properties (header) failed\n"); - goto end; - } - } - - consolePrint("dump process started, please wait. hold b to cancel.\n"); - consoleRefresh(); - - nsp_offset += nsp_header_size; - - // set nsp size - shared_data->total_size = nsp_size; - - // write ncas - for(u32 i = 0; i < title_info->content_count; i++) - { - NcaContext *cur_nca_ctx = &(nca_ctx[i]); - u64 blksize = BLOCK_SIZE; - - memset(&sha256_ctx, 0, sizeof(Sha256Context)); - sha256ContextCreate(&sha256_ctx); - - if (cur_nca_ctx->content_type == NcmContentType_Meta && (!cnmtGenerateNcaPatch(&cnmt_ctx) || !ncaEncryptHeader(cur_nca_ctx))) - { - consolePrint("cnmt generate patch failed\n"); - goto end; - } - - bool dirty_header = ncaIsHeaderDirty(cur_nca_ctx); - - if (output_device == 1) - { - tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, i); - if (!usbSendFilePropertiesCommon(cur_nca_ctx->content_size, tmp_name)) - { - consolePrint("usb send file properties \"%s\" failed\n", tmp_name); - goto end; - } - } - - for(u64 offset = 0; offset < cur_nca_ctx->content_size; offset += blksize, nsp_offset += blksize, shared_data->data_written += blksize) - { - if (shared_data->transfer_cancelled) - { - if (output_device == 1) usbCancelFileTransfer(); - goto end; - } - - if ((cur_nca_ctx->content_size - offset) < blksize) blksize = (cur_nca_ctx->content_size - offset); - - // read nca chunk - if (!ncaReadContentFile(cur_nca_ctx, buf, blksize, offset)) - { - consolePrint("nca read failed at 0x%lX for \"%s\"\n", offset, cur_nca_ctx->content_id_str); - goto end; - } - - if (dirty_header) - { - // write re-encrypted headers - if (!cur_nca_ctx->header_written) ncaWriteEncryptedHeaderDataToMemoryBuffer(cur_nca_ctx, buf, blksize, offset); - - if (cur_nca_ctx->content_type_ctx_patch) - { - // write content type context patch - switch(cur_nca_ctx->content_type) - { - case NcmContentType_Meta: - cnmtWriteNcaPatch(&cnmt_ctx, buf, blksize, offset); - break; - case NcmContentType_Control: - nacpWriteNcaPatch((NacpContext*)cur_nca_ctx->content_type_ctx, buf, blksize, offset); - break; - default: - break; - } - } - - // update flag to avoid entering this code block if it's not needed anymore - dirty_header = (!cur_nca_ctx->header_written || cur_nca_ctx->content_type_ctx_patch); - } - - // update hash calculation - sha256ContextUpdate(&sha256_ctx, buf, blksize); - - // write nca chunk - if (output_device != 1) - { - fwrite(buf, 1, blksize, fd); - } else { - if (!usbSendFileData(buf, blksize)) - { - consolePrint("send file data failed\n"); - goto end; - } - } - } - - // get hash - sha256ContextGetHash(&sha256_ctx, sha256_hash); - - // update content id and hash - ncaUpdateContentIdAndHash(cur_nca_ctx, sha256_hash); - - // update cnmt - if (!cnmtUpdateContentInfo(&cnmt_ctx, cur_nca_ctx)) - { - consolePrint("cnmt update content info failed\n"); - goto end; - } - - // update pfs entry name - if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, i, cur_nca_ctx->content_id_str)) - { - consolePrint("pfs update entry name failed for nca \"%s\"\n", cur_nca_ctx->content_id_str); - goto end; - } - } - - if (append_authoringtool_data) - { - // regenerate cnmt xml - if (!cnmtGenerateAuthoringToolXml(&cnmt_ctx, nca_ctx, title_info->content_count)) - { - consolePrint("cnmt xml #2 failed\n"); - goto end; - } - - // write cnmt xml - if (output_device != 1) - { - fwrite(cnmt_ctx.authoring_tool_xml, 1, cnmt_ctx.authoring_tool_xml_size, fd); - } else { - tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, meta_nca_ctx->content_type_ctx_data_idx); - if (!usbSendFilePropertiesCommon(cnmt_ctx.authoring_tool_xml_size, tmp_name) || !usbSendFileData(cnmt_ctx.authoring_tool_xml, cnmt_ctx.authoring_tool_xml_size)) - { - consolePrint("send \"%s\" failed\n", tmp_name); - goto end; - } - } - - nsp_offset += cnmt_ctx.authoring_tool_xml_size; - shared_data->data_written += cnmt_ctx.authoring_tool_xml_size; - - // update cnmt xml pfs entry name - if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, meta_nca_ctx->content_type_ctx_data_idx, meta_nca_ctx->content_id_str)) - { - consolePrint("pfs update entry name cnmt xml failed\n"); - goto end; - } - } - - // write content type ctx data - for(u32 i = 0; i < limit; i++) - { - NcaContext *cur_nca_ctx = &(nca_ctx[i]); - if (!cur_nca_ctx->content_type_ctx) continue; - - char *authoring_tool_xml = NULL; - u64 authoring_tool_xml_size = 0; - u32 data_idx = cur_nca_ctx->content_type_ctx_data_idx; - - switch(cur_nca_ctx->content_type) - { - case NcmContentType_Program: - { - ProgramInfoContext *cur_program_info_ctx = (ProgramInfoContext*)cur_nca_ctx->content_type_ctx; - authoring_tool_xml = cur_program_info_ctx->authoring_tool_xml; - authoring_tool_xml_size = cur_program_info_ctx->authoring_tool_xml_size; - break; - } - case NcmContentType_Control: - { - NacpContext *cur_nacp_ctx = (NacpContext*)cur_nca_ctx->content_type_ctx; - authoring_tool_xml = cur_nacp_ctx->authoring_tool_xml; - authoring_tool_xml_size = cur_nacp_ctx->authoring_tool_xml_size; - - // loop through available icons - for(u8 j = 0; j < cur_nacp_ctx->icon_count; j++) - { - NacpIconContext *icon_ctx = &(cur_nacp_ctx->icon_ctx[j]); - - // write icon - if (output_device != 1) - { - fwrite(icon_ctx->icon_data, 1, icon_ctx->icon_size, fd); - } else { - tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, data_idx); - if (!usbSendFilePropertiesCommon(icon_ctx->icon_size, tmp_name) || !usbSendFileData(icon_ctx->icon_data, icon_ctx->icon_size)) - { - consolePrint("send \"%s\" failed\n", tmp_name); - goto end; - } - } - - nsp_offset += icon_ctx->icon_size; - shared_data->data_written += icon_ctx->icon_size; - - // update pfs entry name - if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, data_idx++, cur_nca_ctx->content_id_str)) - { - consolePrint("pfs update entry name failed for icon \"%s\" (%u)\n", cur_nca_ctx->content_id_str, icon_ctx->language); - goto end; - } - } - - break; - } - case NcmContentType_LegalInformation: - { - LegalInfoContext *cur_legal_info_ctx = (LegalInfoContext*)cur_nca_ctx->content_type_ctx; - authoring_tool_xml = cur_legal_info_ctx->authoring_tool_xml; - authoring_tool_xml_size = cur_legal_info_ctx->authoring_tool_xml_size; - break; - } - default: - break; - } - - // write xml - if (output_device != 1) - { - fwrite(authoring_tool_xml, 1, authoring_tool_xml_size, fd); - } else { - tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, data_idx); - if (!usbSendFilePropertiesCommon(authoring_tool_xml_size, tmp_name) || !usbSendFileData(authoring_tool_xml, authoring_tool_xml_size)) - { - consolePrint("send \"%s\" failed\n", tmp_name); - goto end; - } - } - - nsp_offset += authoring_tool_xml_size; - shared_data->data_written += authoring_tool_xml_size; - - // update pfs entry name - if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, data_idx, cur_nca_ctx->content_id_str)) - { - consolePrint("pfs update entry name failed for xml \"%s\"\n", cur_nca_ctx->content_id_str); - goto end; - } - } - - if (retrieve_tik_cert) - { - // write ticket - if (output_device != 1) - { - fwrite(tik.data, 1, tik.size, fd); - } else { - tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, pfs_file_ctx.header.entry_count - 2); - if (!usbSendFilePropertiesCommon(tik.size, tmp_name) || !usbSendFileData(tik.data, tik.size)) - { - consolePrint("send \"%s\" failed\n", tmp_name); - goto end; - } - } - - nsp_offset += tik.size; - shared_data->data_written += tik.size; - - // write cert - if (output_device != 1) - { - fwrite(raw_cert_chain, 1, raw_cert_chain_size, fd); - } else { - tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, pfs_file_ctx.header.entry_count - 1); - if (!usbSendFilePropertiesCommon(raw_cert_chain_size, tmp_name) || !usbSendFileData(raw_cert_chain, raw_cert_chain_size)) - { - consolePrint("send \"%s\" failed\n", tmp_name); - goto end; - } - } - - nsp_offset += raw_cert_chain_size; - shared_data->data_written += raw_cert_chain_size; - } - - // write new pfs0 header - if (!pfsWriteFileContextHeaderToMemoryBuffer(&pfs_file_ctx, buf, BLOCK_SIZE, &nsp_header_size)) - { - consolePrint("pfs write header to mem #2 failed\n"); - goto end; - } - - if (output_device != 1) - { - rewind(fd); - fwrite(buf, 1, nsp_header_size, fd); - } else { - if (!usbSendNspHeader(buf, (u32)nsp_header_size)) - { - consolePrint("send nsp header failed\n"); - goto end; - } - } - - shared_data->data_written += nsp_header_size; - - success = true; - -end: - consoleRefresh(); - - if (!success && !shared_data->transfer_cancelled) shared_data->error = true; - - if (fd) - { - fclose(fd); - if (!ums_device && !success) utilsRemoveConcatenationFile(path); - utilsCommitSdCardFileSystemChanges(); - } - - pfsFreeFileContext(&pfs_file_ctx); - - if (raw_cert_chain) free(raw_cert_chain); - - if (legal_info_ctx) - { - for(u32 i = 0; i < legal_info_count; i++) legalInfoFreeContext(&(legal_info_ctx[i])); - free(legal_info_ctx); - } - - if (nacp_ctx) - { - for(u32 i = 0; i < control_count; i++) nacpFreeContext(&(nacp_ctx[i])); - free(nacp_ctx); - } - - if (program_info_ctx) - { - for(u32 i = 0; i < program_count; i++) programInfoFreeContext(&(program_info_ctx[i])); - free(program_info_ctx); - } - - cnmtFreeContext(&cnmt_ctx); - - if (nca_ctx) free(nca_ctx); - - if (path) free(path); - - if (dump_name) free(dump_name); - - if (buf) free(buf); - - threadExit(); -} - -static void nspDump(TitleInfo *title_info) -{ - if (!title_info) return; - - TitleApplicationMetadata *app_metadata = title_info->app_metadata; - - ThreadSharedData shared_data = {0}; - Thread dump_thread = {0}; - - time_t start = 0, btn_cancel_start_tmr = 0, btn_cancel_end_tmr = 0; - bool btn_cancel_cur_state = false, btn_cancel_prev_state = false; - u8 usb_host_speed = UsbHostSpeed_None; - - u64 prev_size = 0; - u8 prev_time = 0, percent = 0; - - u32 output_device = options[8].val; - - consoleClear(); - - consolePrint("%s info:\n\n", title_info->meta_key.type == NcmContentMetaType_Application ? "base application" : \ - (title_info->meta_key.type == NcmContentMetaType_Patch ? "update" : \ - (title_info->meta_key.type == NcmContentMetaType_AddOnContent ? "dlc" : "dlc update"))); - - if (app_metadata) - { - consolePrint("name: %s\n", app_metadata->lang_entry.name); - consolePrint("publisher: %s\n", app_metadata->lang_entry.author); - } - - consolePrint("source storage: %s\n", titleGetNcmStorageIdName(title_info->storage_id)); - consolePrint("title id: %016lX\n", title_info->meta_key.id); - consolePrint("version: %u (%u.%u.%u-%u.%u)\n", title_info->version.value, title_info->version.system_version.major, title_info->version.system_version.minor, title_info->version.system_version.micro, title_info->version.system_version.major_relstep, \ - title_info->version.system_version.minor_relstep); - consolePrint("content count: %u\n", title_info->content_count); - consolePrint("size: %s\n", title_info->size_str); - consolePrint("______________________________\n\n"); - consolePrint("dump options:\n\n"); - for(u32 i = 0; i < (options_count - 1); i++) consolePrint("%s: %s\n", options[i].str, options[i].val ? "yes" : "no"); - - consolePrint("%s: ", options[options_count - 1].str); - switch(options[options_count - 1].val) - { - case 0: - consolePrint("%s\n", DEVOPTAB_SDMC_DEVICE); - break; - case 1: - consolePrint("usb host (pc)\n"); - break; - default: - consolePrint("ums device #%u\n", options[options_count - 1].val - 1); - break; - } - - consolePrint("______________________________\n\n"); - - if (output_device == 1) - { - // make sure we have a valid usb session - consolePrint("waiting for usb connection... "); - - start = time(NULL); - - while(true) - { - time_t now = time(NULL); - if ((now - start) >= 10) break; - - consolePrint("%lu ", now - start); - consoleRefresh(); - - if ((usb_host_speed = usbIsReady())) break; - utilsSleep(1); - } - - consolePrint("\n"); - - if (!usb_host_speed) - { - consolePrint("usb connection failed\n"); - return; - } - } - - consoleRefresh(); - - // create dump thread - shared_data.data = title_info; - utilsCreateThread(&dump_thread, dump_thread_func, &shared_data, 2); - - while(!shared_data.total_size && !shared_data.error) svcSleepThread(10000000); // 10 ms - - if (shared_data.error) - { - utilsJoinThread(&dump_thread); - return; - } - - // start dump - start = time(NULL); - - while(shared_data.data_written < shared_data.total_size) - { - if (shared_data.error) break; - - struct tm ts = {0}; - time_t now = time(NULL); - localtime_r(&now, &ts); - - size_t size = shared_data.data_written; - - utilsScanPads(); - btn_cancel_cur_state = (utilsGetButtonsHeld() & HidNpadButton_B); - - if (btn_cancel_cur_state && btn_cancel_cur_state != btn_cancel_prev_state) - { - btn_cancel_start_tmr = now; - } else - if (btn_cancel_cur_state && btn_cancel_cur_state == btn_cancel_prev_state) - { - btn_cancel_end_tmr = now; - if ((btn_cancel_end_tmr - btn_cancel_start_tmr) >= 3) - { - shared_data.transfer_cancelled = true; - break; - } - } else { - btn_cancel_start_tmr = btn_cancel_end_tmr = 0; - } - - btn_cancel_prev_state = btn_cancel_cur_state; - - if (prev_time == ts.tm_sec || prev_size == size) continue; - - percent = (u8)((size * 100) / shared_data.total_size); - - prev_time = ts.tm_sec; - prev_size = size; - - consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_data.total_size, percent, (now - start)); - consoleRefresh(); - } - - start = (time(NULL) - start); - - consolePrint("\nwaiting for thread to join\n"); - utilsJoinThread(&dump_thread); - consolePrint("dump_thread done: %lu\n", time(NULL)); - - if (shared_data.error) - { - consolePrint("i/o error\n"); - return; - } - - if (shared_data.transfer_cancelled) - { - consolePrint("process cancelled\n"); - return; - } - - consolePrint("process completed in %lu seconds\n", start); -} - -int main(int argc, char *argv[]) -{ - int ret = 0; - - if (!utilsInitializeResources(argc, (const char**)argv)) - { - ret = -1; - goto out; - } - - /* Configure input. */ - /* Up to 8 different, full controller inputs. */ - /* Individual Joy-Cons not supported. */ - padConfigureInput(8, HidNpadStyleSet_NpadFullCtrl); - padInitializeWithMask(&g_padState, 0x1000000FFUL); - - consoleInit(NULL); - - u32 app_count = 0; - TitleApplicationMetadata **app_metadata = NULL; - TitleUserApplicationData user_app_data = {0}; - TitleInfo *title_info = NULL; - - u32 menu = 0, selected_idx = 0, scroll = 0, page_size = 30; - - u32 title_idx = 0, title_scroll = 0; - u32 type_idx = 0, type_scroll = 0; - u32 list_count = 0, list_idx = 0; - - u64 device_total_fs_size = 0, device_free_fs_size = 0; - char device_total_fs_size_str[36] = {0}, device_free_fs_size_str[32] = {0}, device_info[0x300] = {0}; - bool device_retrieved_size = false, device_retrieved_info = false; - - bool applet_status = true; - - app_metadata = titleGetApplicationMetadataEntries(false, &app_count); - if (!app_metadata || !app_count) - { - consolePrint("app metadata failed\n"); - goto out2; - } - - consolePrint("app metadata succeeded\n"); - consoleRefresh(); - - ums_devices = umsGetDevices(&ums_device_count); - - while((applet_status = appletMainLoop())) - { - consoleClear(); - - consolePrint("press b to %s.\n", menu == 0 ? "exit" : "go back"); - if (ums_device_count) consolePrint("press x to safely remove all ums devices.\n"); - consolePrint("______________________________\n\n"); - - if (menu == 0) - { - consolePrint("title: %u / %u\n", selected_idx + 1, app_count); - consolePrint("selected title: %016lX - %s\n", app_metadata[selected_idx]->title_id, app_metadata[selected_idx]->lang_entry.name); - } else { - consolePrint("title info:\n\n"); - consolePrint("name: %s\n", app_metadata[title_idx]->lang_entry.name); - consolePrint("publisher: %s\n", app_metadata[title_idx]->lang_entry.author); - consolePrint("title id: %016lX\n", app_metadata[title_idx]->title_id); - - if (menu == 2) - { - consolePrint("______________________________\n\n"); - - if (title_info->previous || title_info->next) - { - consolePrint("press zl/l and/or zr/r to change the selected title\n"); - consolePrint("title: %u / %u\n", list_idx, list_count); - consolePrint("______________________________\n\n"); - } - - consolePrint("selected %s info:\n\n", title_info->meta_key.type == NcmContentMetaType_Application ? "base application" : \ - (title_info->meta_key.type == NcmContentMetaType_Patch ? "update" : \ - (title_info->meta_key.type == NcmContentMetaType_AddOnContent ? "dlc" : "dlc update"))); - consolePrint("source storage: %s\n", titleGetNcmStorageIdName(title_info->storage_id)); - if (title_info->meta_key.type != NcmContentMetaType_Application) consolePrint("title id: %016lX\n", title_info->meta_key.id); - consolePrint("version: %u (%u.%u.%u-%u.%u)\n", title_info->version.value, title_info->version.system_version.major, title_info->version.system_version.minor, \ - title_info->version.system_version.micro, title_info->version.system_version.major_relstep, title_info->version.system_version.minor_relstep); - consolePrint("content count: %u\n", title_info->content_count); - consolePrint("size: %s\n", title_info->size_str); - } - } - - consolePrint("______________________________\n\n"); - - u32 max_val = (menu == 0 ? app_count : (menu == 1 ? dump_type_strings_count : (1 + options_count))); - for(u32 i = scroll; i < max_val; i++) - { - if (i >= (scroll + page_size)) break; - - consolePrint("%s", i == selected_idx ? " -> " : " "); - - if (menu == 0) - { - consolePrint("%016lX - %s\n", app_metadata[i]->title_id, app_metadata[i]->lang_entry.name); - } else - if (menu == 1) - { - consolePrint("%s\n", dump_type_strings[i]); - } else - if (menu == 2) - { - if (i == 0) - { - consolePrint("start nsp dump\n"); - } else - if (i < options_count) - { - consolePrint("%s: < %s >\n", options[i - 1].str, options[i - 1].val ? "yes" : "no"); - } else { - consolePrint("%s: ", options[i - 1].str); - - u32 output_device = options[i - 1].val; - - if (output_device != 1) - { - if (!device_retrieved_size) - { - sprintf(device_total_fs_size_str, "%s/", output_device == 0 ? DEVOPTAB_SDMC_DEVICE : ums_devices[output_device - 2].name); - utilsGetFileSystemStatsByPath(device_total_fs_size_str, &device_total_fs_size, &device_free_fs_size); - utilsGenerateFormattedSizeString(device_total_fs_size, device_total_fs_size_str, sizeof(device_total_fs_size_str)); - utilsGenerateFormattedSizeString(device_free_fs_size, device_free_fs_size_str, sizeof(device_free_fs_size_str)); - device_retrieved_size = true; - } - - if (output_device == 0) - { - consolePrint("< sdmc: (%s / %s) >\n", device_free_fs_size_str, device_total_fs_size_str); - } else { - UsbHsFsDevice *ums_device = &(ums_devices[output_device - 2]); - - if (!device_retrieved_info) - { - if (ums_device->product_name[0]) - { - sprintf(device_info, "%s, LUN %u, FS #%u, %s", ums_device->product_name, ums_device->lun, ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(ums_device->fs_type)); - } else { - sprintf(device_info, "LUN %u, FS #%u, %s", ums_device->lun, ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(ums_device->fs_type)); - } - - device_retrieved_info = true; - } - - consolePrint("< %s (%s) (%s / %s) >", ums_device->name, device_info, device_free_fs_size_str, device_total_fs_size_str); - } - } else { - consolePrint("< usb host (pc) >"); - device_retrieved_size = device_retrieved_info = false; - } - } - } - } - - consolePrint("\n"); - consoleRefresh(); - - bool data_update = false; - u64 btn_down = 0, btn_held = 0; - - while((applet_status = appletMainLoop())) - { - utilsScanPads(); - btn_down = utilsGetButtonsDown(); - btn_held = utilsGetButtonsHeld(); - if (btn_down || btn_held) break; - - if (titleIsGameCardInfoUpdated()) - { - free(app_metadata); - - app_metadata = titleGetApplicationMetadataEntries(false, &app_count); - if (!app_metadata) - { - consolePrint("\napp metadata failed\n"); - goto out2; - } - - menu = selected_idx = scroll = 0; - - title_idx = title_scroll = 0; - type_idx = type_scroll = 0; - list_count = list_idx = 0; - - data_update = true; - - break; - } - - if (umsIsDeviceInfoUpdated()) - { - free(ums_devices); - - ums_devices = umsGetDevices(&ums_device_count); - - options[options_count - 1].val = 0; - device_retrieved_size = device_retrieved_info = false; - - data_update = true; - - break; - } - } - - if (!applet_status) break; - - if (data_update) continue; - - if (btn_down & HidNpadButton_A) - { - bool error = false; - - if (menu == 0) - { - title_idx = selected_idx; - title_scroll = scroll; - } else - if (menu == 1) - { - type_idx = selected_idx; - type_scroll = scroll; - } - - menu++; - - if (menu == 3 && selected_idx != 0) - { - menu--; - continue; - } - - if (menu == 1) - { - if (!titleGetUserApplicationData(app_metadata[title_idx]->title_id, &user_app_data)) - { - consolePrint("\nget user application data failed!\n"); - error = true; - } - } else - if (menu == 2) - { - if ((type_idx == 0 && !user_app_data.app_info) || (type_idx == 1 && !user_app_data.patch_info) || (type_idx == 2 && !user_app_data.aoc_info) || (type_idx == 3 && !user_app_data.aoc_patch_info)) - { - consolePrint("\nthe selected title doesn't have available %s data\n", type_idx == 0 ? "base application" : (type_idx == 1 ? "update" : (type_idx == 2 ? "dlc" : "dlc update"))); - error = true; - } else { - title_info = (type_idx == 0 ? user_app_data.app_info : (type_idx == 1 ? user_app_data.patch_info : (type_idx == 2 ? user_app_data.aoc_info : user_app_data.aoc_patch_info))); - list_count = titleGetCountFromInfoBlock(title_info); - list_idx = 1; - } - } else - if (menu == 3) - { - utilsSetLongRunningProcessState(true); - nspDump(title_info); - utilsSetLongRunningProcessState(false); - device_retrieved_size = false; - } - - if (error || menu >= 3) - { - consolePrint("press any button to continue\n"); - consoleRefresh(); - utilsWaitForButtonPress(0); - menu--; - } else { - selected_idx = scroll = 0; - } - } else - if ((btn_down & HidNpadButton_Down) || (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown))) - { - selected_idx++; - - if (selected_idx >= max_val) - { - if (btn_down & HidNpadButton_Down) - { - selected_idx = scroll = 0; - } else { - selected_idx = (max_val - 1); - } - } else - if (selected_idx >= (scroll + (page_size / 2)) && max_val > (scroll + page_size)) - { - scroll++; - } - } else - if ((btn_down & HidNpadButton_Up) || (btn_held & (HidNpadButton_StickLUp | HidNpadButton_StickRUp))) - { - selected_idx--; - - if (selected_idx == UINT32_MAX) - { - if (btn_down & HidNpadButton_Up) - { - selected_idx = (max_val - 1); - scroll = (max_val >= page_size ? (max_val - page_size) : 0); - } else { - selected_idx = 0; - } - } else - if (selected_idx < (scroll + (page_size / 2)) && scroll > 0) - { - scroll--; - } - } else - if (btn_down & HidNpadButton_B) - { - menu--; - - if (menu == UINT32_MAX) - { - break; - } else { - selected_idx = (menu == 0 ? title_idx : type_idx); - scroll = (menu == 0 ? title_scroll : type_scroll); - if (menu == 0) titleFreeUserApplicationData(&user_app_data); - } - } else - if ((btn_down & (HidNpadButton_Left | HidNpadButton_Right)) && menu == 2 && selected_idx != 0) - { - if (selected_idx < options_count) - { - options[selected_idx - 1].val ^= 1; - } else { - bool left = (btn_down & HidNpadButton_Left); - u32 *output_device = &(options[selected_idx - 1].val), orig_output_device = *output_device; - - if (left) - { - (*output_device)--; - if (*output_device == UINT32_MAX) *output_device = (ums_device_count + 1); - } else { - (*output_device)++; - if (*output_device > (ums_device_count + 1)) *output_device = 0; - } - - if (*output_device != orig_output_device) device_retrieved_size = device_retrieved_info = false; - } - } else - if ((btn_down & (HidNpadButton_L | HidNpadButton_ZL)) && menu == 2 && title_info->previous) - { - title_info = title_info->previous; - list_idx--; - } else - if ((btn_down & (HidNpadButton_R | HidNpadButton_ZR)) && menu == 2 && title_info->next) - { - title_info = title_info->next; - list_idx++; - } else - if ((btn_down & HidNpadButton_X) && ums_device_count) - { - for(u32 i = 0; i < ums_device_count; i++) usbHsFsUnmountDevice(&(ums_devices[i]), false); - - options[options_count - 1].val = 0; - - free(ums_devices); - ums_devices = NULL; - - ums_device_count = 0; - } - - if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp)) svcSleepThread(50000000); // 50 ms - } - - if (!applet_status) menu = UINT32_MAX; - -out2: - consoleRefresh(); - - if (menu != UINT32_MAX) - { - consolePrint("press any button to exit\n"); - utilsWaitForButtonPress(0); - } - - if (ums_devices) free(ums_devices); - - if (app_metadata) free(app_metadata); - -out: - utilsCloseResources(); - - consoleExit(NULL); - - return ret; -} diff --git a/code_templates/nxdt_rw_poc.c b/code_templates/nxdt_rw_poc.c new file mode 100644 index 0000000..ea783c9 --- /dev/null +++ b/code_templates/nxdt_rw_poc.c @@ -0,0 +1,4197 @@ +/* + * main.c + * + * Copyright (c) 2020-2023, DarkMatterCore . + * + * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). + * + * nxdumptool is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nxdumptool is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "nxdt_utils.h" +#include "gamecard.h" +#include "title.h" +#include "cnmt.h" +#include "program_info.h" +#include "nacp.h" +#include "legal_info.h" +#include "cert.h" +#include "usb.h" + +#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE +#define WAIT_TIME_LIMIT 30 +#define OUTDIR "nxdt_rw_poc" + +/* Type definitions. */ + +typedef struct _Menu Menu; + +typedef u32 (*MenuElementOptionGetterFunction)(void); +typedef void (*MenuElementOptionSetterFunction)(u32 idx); +typedef bool (*MenuElementFunction)(void *userdata); + +typedef struct { + u32 selected; ///< Used to keep track of the selected option. + MenuElementOptionGetterFunction getter_func; ///< Pointer to a function to be called the first time an option value is loaded. Should be set to NULL if not used. + MenuElementOptionSetterFunction setter_func; ///< Pointer to a function to be called each time a new option value is selected. Should be set to NULL if not used. + char **options; ///< Pointer to multiple char pointers with strings representing options. Last element must be set to NULL. +} MenuElementOption; + +typedef struct { + char *str; ///< Pointer to a string to be printed for this menu element. + Menu *child_menu; ///< Pointer to a child Menu element. Must be set to NULL if task_func != NULL. + MenuElementFunction task_func; ///< Pointer to a function to be called by this element. Must be set to NULL if child_menu != NULL. + MenuElementOption *element_options; ///< Options for this menu element. Should be set to NULL if not used. + void *userdata; ///< Optional userdata pointer associated with this element. This is always passed to task_func as its only argument. + ///< This may or may be not used by the menu handler. Should be set to NULL if not used. +} MenuElement; + +struct _Menu { + u32 id; ///< Identifier. + struct _Menu *parent; ///< Set to NULL in the root menu element. + u32 selected, scroll; ///< Used to keep track of the selected element and scroll values. + MenuElement **elements; ///< Element info from this menu. Last element must be set to NULL. +}; + +typedef enum { + MenuId_Root = 0, + MenuId_GameCard = 1, + MenuId_XCI = 2, + MenuId_HFS = 3, + MenuId_UserTitles = 4, + MenuId_UserTitlesSubMenu = 5, + MenuId_NSPTitleTypes = 6, + MenuId_NSP = 7, + MenuId_TicketTitleTypes = 8, + MenuId_Ticket = 9, + MenuId_NCATitleTypes = 10, + MenuId_NCA = 11, + MenuId_NCAFsSection = 12, + MenuId_Count = 13 +} MenuId; + +typedef struct +{ + FILE *fp; + void *data; + size_t data_size; + size_t data_written; + size_t total_size; + bool read_error; + bool write_error; + bool transfer_cancelled; +} SharedThreadData; + +typedef struct { + SharedThreadData shared_thread_data; + u32 xci_crc, full_xci_crc; +} XciThreadData; + +typedef struct { + SharedThreadData shared_thread_data; + HashFileSystemContext *hfs_ctx; +} HfsThreadData; + +typedef struct { + void *data; + size_t data_written; + size_t total_size; + bool error; + bool transfer_cancelled; +} NspThreadData; + +typedef struct { + TitleInfo *title_info; + u32 content_idx; +} NcaUserData; + +typedef struct { + SharedThreadData shared_thread_data; + NcaContext *nca_ctx; +} NcaThreadData; + +/* Function prototypes. */ + +static void utilsScanPads(void); +static u64 utilsGetButtonsDown(void); +static u64 utilsGetButtonsHeld(void); +static void utilsWaitForButtonPress(u64 flag); + +static void consolePrint(const char *text, ...); +static void consoleRefresh(void); + +static u32 menuGetElementCount(const Menu *menu); + +void freeStorageList(void); +void updateStorageList(void); + +void freeTitleList(void); +void updateTitleList(void); + +void freeNcaList(void); +void updateNcaList(TitleInfo *title_info); + +NX_INLINE bool useUsbHost(void); + +static bool waitForGameCard(void); +static bool waitForUsb(void); + +static char *generateOutputGameCardFileName(const char *subdir, const char *extension, bool use_nacp_name); +static char *generateOutputTitleFileName(TitleInfo *title_info, const char *subdir, const char *extension); + +static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out); + +static bool saveGameCardImage(void *userdata); +static bool saveGameCardHeader(void *userdata); +static bool saveGameCardCardInfo(void *userdata); +static bool saveGameCardCertificate(void *userdata); +static bool saveGameCardInitialData(void *userdata); +static bool saveGameCardSpecificData(void *userdata); +static bool saveGameCardIdSet(void *userdata); +static bool saveGameCardHfsPartition(void *userdata); +static bool saveGameCardRawHfsPartition(HashFileSystemContext *hfs_ctx); +static bool saveGameCardExtractedHfsPartition(HashFileSystemContext *hfs_ctx); + +static bool saveConsoleLafwBlob(void *userdata); + +static bool saveNintendoSubmissionPackage(void *userdata); + +static bool saveTicket(void *userdata); + +static bool saveNintendoContentArchive(void *userdata); + +static void xciReadThreadFunc(void *arg); + +static void rawHfsReadThreadFunc(void *arg); +static void extractedHfsReadThreadFunc(void *arg); + +static void ncaReadThreadFunc(void *arg); + +static void genericWriteThreadFunc(void *arg); + +static bool spanDumpThreads(ThreadFunc read_func, ThreadFunc write_func, void *arg); + +static void nspThreadFunc(void *arg); + +static u32 getOutputStorageOption(void); +static void setOutputStorageOption(u32 idx); + +static u32 getGameCardPrependKeyAreaOption(void); +static void setGameCardPrependKeyAreaOption(u32 idx); + +static u32 getGameCardKeepCertificateOption(void); +static void setGameCardKeepCertificateOption(u32 idx); + +static u32 getGameCardTrimDumpOption(void); +static void setGameCardTrimDumpOption(u32 idx); + +static u32 getGameCardCalculateChecksumOption(void); +static void setGameCardCalculateChecksumOption(u32 idx); + +static u32 getGameCardWriteRawHfsPartitionOption(void); +static void setGameCardWriteRawHfsPartitionOption(u32 idx); + +static u32 getNspSetDownloadDistributionOption(void); +static void setNspSetDownloadDistributionOption(u32 idx); + +static u32 getNspRemoveConsoleDataOption(void); +static void setNspRemoveConsoleDataOption(u32 idx); + +static u32 getNspRemoveTitlekeyCryptoOption(void); +static void setNspRemoveTitlekeyCryptoOption(u32 idx); + +static u32 getNspDisableLinkedAccountRequirementOption(void); +static void setNspDisableLinkedAccountRequirementOption(u32 idx); + +static u32 getNspEnableScreenshotsOption(void); +static void setNspEnableScreenshotsOption(u32 idx); + +static u32 getNspEnableVideoCaptureOption(void); +static void setNspEnableVideoCaptureOption(u32 idx); + +static u32 getNspDisableHdcpOption(void); +static void setNspDisableHdcpOption(u32 idx); + +static u32 getNspAppendAuthoringToolDataOption(void); +static void setNspAppendAuthoringToolDataOption(u32 idx); + +static u32 getTicketRemoveConsoleDataOption(void); +static void setTicketRemoveConsoleDataOption(u32 idx); + +/* Global variables. */ + +bool g_borealisInitialized = false; + +static PadState g_padState = {0}; + +static char *g_noYesStrings[] = { "no", "yes", NULL }; + +static bool g_appletStatus = true; + +static UsbHsFsDevice *g_umsDevices = NULL; +static u32 g_umsDeviceCount = 0; +static char **g_storageOptions = NULL; + +static MenuElementOption g_storageMenuElementOption = { + .selected = 0, + .getter_func = &getOutputStorageOption, + .setter_func = &setOutputStorageOption, + .options = NULL +}; + +static MenuElement *g_xciMenuElements[] = { + &(MenuElement){ + .str = "start xci dump", + .child_menu = NULL, + .task_func = &saveGameCardImage, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "prepend key area", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 0, + .getter_func = &getGameCardPrependKeyAreaOption, + .setter_func = &setGameCardPrependKeyAreaOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "keep certificate", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 0, + .getter_func = &getGameCardKeepCertificateOption, + .setter_func = &setGameCardKeepCertificateOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "trim dump", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 0, + .getter_func = &getGameCardTrimDumpOption, + .setter_func = &setGameCardTrimDumpOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "calculate checksum", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 1, + .getter_func = &getGameCardCalculateChecksumOption, + .setter_func = &setGameCardCalculateChecksumOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "output storage", + .child_menu = NULL, + .task_func = NULL, + .element_options = &g_storageMenuElementOption, + .userdata = NULL + }, + NULL +}; + +static u32 g_hfsRootPartition = HashFileSystemPartitionType_Root; +static u32 g_hfsUpdatePartition = HashFileSystemPartitionType_Update; +static u32 g_hfsLogoPartition = HashFileSystemPartitionType_Logo; +static u32 g_hfsNormalPartition = HashFileSystemPartitionType_Normal; +static u32 g_hfsSecurePartition = HashFileSystemPartitionType_Secure; + +static MenuElement *g_gameCardHfsMenuElements[] = { + &(MenuElement){ + .str = "dump root hfs partition", + .child_menu = NULL, + .task_func = &saveGameCardHfsPartition, + .element_options = NULL, + .userdata = &g_hfsRootPartition + }, + &(MenuElement){ + .str = "dump update hfs partition", + .child_menu = NULL, + .task_func = &saveGameCardHfsPartition, + .element_options = NULL, + .userdata = &g_hfsUpdatePartition + }, + &(MenuElement){ + .str = "dump logo hfs partition", + .child_menu = NULL, + .task_func = &saveGameCardHfsPartition, + .element_options = NULL, + .userdata = &g_hfsLogoPartition + }, + &(MenuElement){ + .str = "dump normal hfs partition", + .child_menu = NULL, + .task_func = &saveGameCardHfsPartition, + .element_options = NULL, + .userdata = &g_hfsNormalPartition + }, + &(MenuElement){ + .str = "dump secure hfs partition", + .child_menu = NULL, + .task_func = &saveGameCardHfsPartition, + .element_options = NULL, + .userdata = &g_hfsSecurePartition + }, + &(MenuElement){ + .str = "write raw hfs partition", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 0, + .getter_func = &getGameCardWriteRawHfsPartitionOption, + .setter_func = &setGameCardWriteRawHfsPartitionOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "output storage", + .child_menu = NULL, + .task_func = NULL, + .element_options = &g_storageMenuElementOption, + .userdata = NULL + }, + NULL +}; + +static MenuElement *g_gameCardMenuElements[] = { + &(MenuElement){ + .str = "dump gamecard image (xci)", + .child_menu = &(Menu){ + .id = MenuId_XCI, + .parent = NULL, + .selected = 0, + .scroll = 0, + .elements = g_xciMenuElements + }, + .task_func = NULL, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "dump gamecard header", + .child_menu = NULL, + .task_func = &saveGameCardHeader, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "dump gamecard cardinfo", + .child_menu = NULL, + .task_func = &saveGameCardCardInfo, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "dump gamecard certificate", + .child_menu = NULL, + .task_func = &saveGameCardCertificate, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "dump gamecard initial data", + .child_menu = NULL, + .task_func = &saveGameCardInitialData, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "dump gamecard specific data", + .child_menu = NULL, + .task_func = &saveGameCardSpecificData, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "dump gamecard id set", + .child_menu = NULL, + .task_func = &saveGameCardIdSet, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "dump hfs partitions", + .child_menu = &(Menu){ + .id = MenuId_HFS, + .parent = NULL, + .selected = 0, + .scroll = 0, + .elements = g_gameCardHfsMenuElements + }, + .task_func = NULL, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "dump console lafw blob", + .child_menu = NULL, + .task_func = &saveConsoleLafwBlob, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "output storage", + .child_menu = NULL, + .task_func = NULL, + .element_options = &g_storageMenuElementOption, + .userdata = NULL + }, + NULL +}; + +static MenuElement *g_nspMenuElements[] = { + &(MenuElement){ + .str = "start nsp dump", + .child_menu = NULL, + .task_func = &saveNintendoSubmissionPackage, + .element_options = NULL, + .userdata = NULL // Dynamically set to the TitleInfo object from the title to dump + }, + &(MenuElement){ + .str = "nca: set content distribution type to \"download\"", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 0, + .getter_func = &getNspSetDownloadDistributionOption, + .setter_func = &setNspSetDownloadDistributionOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "tik: remove console specific data", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 0, + .getter_func = &getNspRemoveConsoleDataOption, + .setter_func = &setNspRemoveConsoleDataOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "nca/tik: remove titlekey crypto (overrides previous option)", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 0, + .getter_func = &getNspRemoveTitlekeyCryptoOption, + .setter_func = &setNspRemoveTitlekeyCryptoOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "nacp: disable linked account requirement", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 1, + .getter_func = &getNspDisableLinkedAccountRequirementOption, + .setter_func = &setNspDisableLinkedAccountRequirementOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "nacp: enable screenshots", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 1, + .getter_func = &getNspEnableScreenshotsOption, + .setter_func = &setNspEnableScreenshotsOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "nacp: enable video capture", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 1, + .getter_func = &getNspEnableVideoCaptureOption, + .setter_func = &setNspEnableVideoCaptureOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "nacp: disable hdcp", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 1, + .getter_func = &getNspDisableHdcpOption, + .setter_func = &setNspDisableHdcpOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "nsp: append authoringtool data", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 1, + .getter_func = &getNspAppendAuthoringToolDataOption, + .setter_func = &setNspAppendAuthoringToolDataOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "output storage", + .child_menu = NULL, + .task_func = NULL, + .element_options = &g_storageMenuElementOption, + .userdata = NULL + }, + NULL +}; + +static Menu g_nspMenu = { + .id = MenuId_NSP, + .parent = NULL, + .selected = 0, + .scroll = 0, + .elements = g_nspMenuElements +}; + +static MenuElement *g_ticketMenuElements[] = { + &(MenuElement){ + .str = "start ticket dump", + .child_menu = NULL, + .task_func = &saveTicket, + .element_options = NULL, + .userdata = NULL // Dynamically set to the TitleInfo object from the title to dump + }, + &(MenuElement){ + .str = "remove console specific data", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 0, + .getter_func = &getTicketRemoveConsoleDataOption, + .setter_func = &setTicketRemoveConsoleDataOption, + .options = g_noYesStrings + }, + .userdata = NULL + }, + &(MenuElement){ + .str = "output storage", + .child_menu = NULL, + .task_func = NULL, + .element_options = &g_storageMenuElementOption, + .userdata = NULL + }, + NULL +}; + +static Menu g_ticketMenu = { + .id = MenuId_Ticket, + .parent = NULL, + .selected = 0, + .scroll = 0, + .elements = g_ticketMenuElements +}; + +static MenuElement **g_ncaMenuElements = NULL; + +// Dynamically populated using g_ncaMenuElements. +static Menu g_ncaMenu = { + .id = MenuId_NCA, + .parent = NULL, + .selected = 0, + .scroll = 0, + .elements = NULL +}; + +static u32 g_metaTypeApplication = NcmContentMetaType_Application; +static u32 g_metaTypePatch = NcmContentMetaType_Patch; +static u32 g_metaTypeAOC = NcmContentMetaType_AddOnContent; +static u32 g_metaTypeAOCPatch = NcmContentMetaType_DataPatch; + +static MenuElement *g_titleTypesMenuElements[] = { + &(MenuElement){ + .str = "dump base application", + .child_menu = NULL, // Dynamically set + .task_func = NULL, + .element_options = NULL, + .userdata = &g_metaTypeApplication + }, + &(MenuElement){ + .str = "dump update", + .child_menu = NULL, // Dynamically set + .task_func = NULL, + .element_options = NULL, + .userdata = &g_metaTypePatch + }, + &(MenuElement){ + .str = "dump dlc", + .child_menu = NULL, // Dynamically set + .task_func = NULL, + .element_options = NULL, + .userdata = &g_metaTypeAOC + }, + &(MenuElement){ + .str = "dump dlc update", + .child_menu = NULL, // Dynamically set + .task_func = NULL, + .element_options = NULL, + .userdata = &g_metaTypeAOCPatch + }, + NULL +}; + +static MenuElement *g_userTitlesSubMenuElements[] = { + &(MenuElement){ + .str = "nsp dump options", + .child_menu = &(Menu){ + .id = MenuId_NSPTitleTypes, + .parent = NULL, + .selected = 0, + .scroll = 0, + .elements = g_titleTypesMenuElements + }, + .task_func = NULL, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "ticket dump options", + .child_menu = &(Menu){ + .id = MenuId_TicketTitleTypes, + .parent = NULL, + .selected = 0, + .scroll = 0, + .elements = g_titleTypesMenuElements + }, + .task_func = NULL, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "nca dump options", + .child_menu = &(Menu){ + .id = MenuId_NCATitleTypes, + .parent = NULL, + .selected = 0, + .scroll = 0, + .elements = g_titleTypesMenuElements + }, + .task_func = NULL, + .element_options = NULL, + .userdata = NULL + }, + NULL +}; + +// Dynamically set as child_menu for all g_userTitlesMenuElements entries. +static Menu g_userTitlesSubMenu = { + .id = MenuId_UserTitlesSubMenu, + .parent = NULL, + .selected = 0, + .scroll = 0, + .elements = g_userTitlesSubMenuElements +}; + +static MenuElement **g_userTitlesMenuElements = NULL; + +// Dynamically populated using g_userTitlesMenuElements. +static Menu g_userTitlesMenu = { + .id = MenuId_UserTitles, + .parent = NULL, + .selected = 0, + .scroll = 0, + .elements = NULL +}; + +static MenuElement *g_rootMenuElements[] = { + &(MenuElement){ + .str = "gamecard menu", + .child_menu = &(Menu){ + .id = MenuId_GameCard, + .parent = NULL, + .selected = 0, + .scroll = 0, + .elements = g_gameCardMenuElements + }, + .task_func = NULL, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "user titles menu", + .child_menu = &g_userTitlesMenu, + .task_func = NULL, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "system titles menu", + .child_menu = NULL, + .task_func = NULL, + .element_options = NULL, + .userdata = NULL + }, + NULL +}; + +static Menu g_rootMenu = { + .id = MenuId_Root, + .parent = NULL, + .selected = 0, + .scroll = 0, + .elements = g_rootMenuElements +}; + +static Mutex g_conMutex = 0, g_fileMutex = 0; +static CondVar g_readCondvar = 0, g_writeCondvar = 0; + +static char path[FS_MAX_PATH] = {0}; + +int main(int argc, char *argv[]) +{ + int ret = 0; + + if (!utilsInitializeResources(argc, (const char**)argv)) + { + ret = -1; + goto end; + } + + /* Configure input. */ + /* Up to 8 different, full controller inputs. */ + /* Individual Joy-Cons not supported. */ + padConfigureInput(8, HidNpadStyleSet_NpadFullCtrl); + padInitializeWithMask(&g_padState, 0x1000000FFUL); + + consoleInit(NULL); + + updateStorageList(); + + updateTitleList(); + + Menu *cur_menu = &g_rootMenu; + u32 element_count = menuGetElementCount(cur_menu), page_size = 30; + + TitleApplicationMetadata *app_metadata = NULL; + + TitleUserApplicationData user_app_data = {0}; + + TitleInfo *title_info = NULL; + u32 title_info_idx = 0, title_info_count = 0; + + while(appletMainLoop()) + { + MenuElement *selected_element = cur_menu->elements[cur_menu->selected]; + MenuElementOption *selected_element_options = selected_element->element_options; + + if (cur_menu->id == MenuId_UserTitlesSubMenu && selected_element->child_menu) + { + /* Set title types child menu pointer if we're currently at the user titles submenu. */ + u32 child_id = selected_element->child_menu->id; + + g_titleTypesMenuElements[0]->child_menu = g_titleTypesMenuElements[1]->child_menu = \ + g_titleTypesMenuElements[2]->child_menu = g_titleTypesMenuElements[3]->child_menu = (child_id == MenuId_NSPTitleTypes ? &g_nspMenu : \ + (child_id == MenuId_TicketTitleTypes ? &g_ticketMenu : \ + (child_id == MenuId_NCATitleTypes ? &g_ncaMenu : NULL))); + } + + consoleClear(); + consolePrint("______________________________\n\n"); + consolePrint("press b to %s.\n", cur_menu->parent ? "go back" : "exit"); + if (g_umsDeviceCount) consolePrint("press x to safely remove all ums devices\n"); + consolePrint("______________________________\n\n"); + + if (cur_menu->id == MenuId_UserTitles) + { + app_metadata = (TitleApplicationMetadata*)selected_element->userdata; + + consolePrint("title: %u / %u\n", cur_menu->selected + 1, element_count); + consolePrint("selected title: %016lX - %s\n", app_metadata->title_id, selected_element->str); + consolePrint("______________________________\n\n"); + } else + if (cur_menu->id >= MenuId_UserTitlesSubMenu && cur_menu->id < MenuId_Count) + { + consolePrint("title info:\n\n"); + consolePrint("name: %s\n", app_metadata->lang_entry.name); + consolePrint("publisher: %s\n", app_metadata->lang_entry.author); + consolePrint("title id: %016lX\n", app_metadata->title_id); + consolePrint("______________________________\n\n"); + + if (cur_menu->id == MenuId_NSP || cur_menu->id == MenuId_Ticket || cur_menu->id == MenuId_NCA) + { + if (title_info->previous || title_info->next) + { + consolePrint("press l/zl and/or r/zr to change the selected title\n"); + consolePrint("title: %u / %u\n", title_info_idx, title_info_count); + consolePrint("______________________________\n\n"); + } + + consolePrint("selected title info:\n\n"); + consolePrint("title id: %016lX\n", title_info->meta_key.id); + consolePrint("type: %s\n", titleGetNcmContentMetaTypeName(title_info->meta_key.type)); + consolePrint("source storage: %s\n", titleGetNcmStorageIdName(title_info->storage_id)); + consolePrint("version: %u (%u.%u.%u-%u.%u)\n", title_info->version.value, title_info->version.system_version.major, title_info->version.system_version.minor, \ + title_info->version.system_version.micro, title_info->version.system_version.major_relstep, title_info->version.system_version.minor_relstep); + consolePrint("content count: %u\n", title_info->content_count); + consolePrint("size: %s\n", title_info->size_str); + consolePrint("______________________________\n\n"); + + if (cur_menu->id == MenuId_NSP) g_nspMenuElements[0]->userdata = title_info; + + if (cur_menu->id == MenuId_Ticket) g_ticketMenuElements[0]->userdata = title_info; + } + } + + for(u32 i = cur_menu->scroll; i < element_count; i++) + { + if (i >= (cur_menu->scroll + page_size)) break; + + MenuElement *cur_element = cur_menu->elements[i]; + MenuElementOption *cur_options = cur_element->element_options; + TitleApplicationMetadata *cur_app_metadata = (cur_menu->id == MenuId_UserTitles ? (TitleApplicationMetadata*)cur_element->userdata : NULL); + + consolePrint("%s", i == cur_menu->selected ? " -> " : " "); + if (cur_app_metadata) consolePrint("%016lX - ", cur_app_metadata->title_id); + consolePrint("%s", cur_element->str); + + if (cur_options) + { + if (cur_options->getter_func) + { + cur_options->selected = cur_options->getter_func(); + cur_options->getter_func = NULL; + } + + consolePrint(": "); + if (cur_options->selected > 0) consolePrint("< "); + consolePrint("%s", cur_options->options[cur_options->selected]); + if (cur_options->options[cur_options->selected + 1]) consolePrint(" >"); + } + + consolePrint("\n"); + } + + if (!element_count) consolePrint("no elements available! press b to go back"); + + consolePrint("\n"); + consoleRefresh(); + + bool data_update = false; + u64 btn_down = 0, btn_held = 0; + + while((g_appletStatus = appletMainLoop())) + { + utilsScanPads(); + btn_down = utilsGetButtonsDown(); + btn_held = utilsGetButtonsHeld(); + if (btn_down || btn_held) break; + + if (umsIsDeviceInfoUpdated()) + { + updateStorageList(); + data_update = true; + break; + } + + if (titleIsGameCardInfoUpdated()) + { + updateTitleList(); + data_update = true; + break; + } + } + + if (!g_appletStatus) break; + + if (data_update) continue; + + if (btn_down & HidNpadButton_A) + { + Menu *child_menu = selected_element->child_menu; + + if (child_menu) + { + bool error = false; + + /* Only change menus if a valid ID was set. */ + if (child_menu->id == MenuId_Root || child_menu->id >= MenuId_Count || child_menu->id == cur_menu->id) continue; + + /* Retrieve extra data based on the current menu ID. */ + + if (child_menu->id == MenuId_UserTitlesSubMenu) + { + error = !titleGetUserApplicationData(app_metadata->title_id, &user_app_data); + if (error) consolePrint("\nfailed to get user application data for %016lX!\n", app_metadata->title_id); + } else + if (child_menu->id == MenuId_NSP || child_menu->id == MenuId_Ticket || child_menu->id == MenuId_NCA) + { + u32 title_type = *((u32*)selected_element->userdata); + + switch(title_type) + { + case NcmContentMetaType_Application: + title_info = user_app_data.app_info; + break; + case NcmContentMetaType_Patch: + title_info = user_app_data.patch_info; + break; + case NcmContentMetaType_AddOnContent: + title_info = user_app_data.aoc_info; + break; + case NcmContentMetaType_DataPatch: + title_info = user_app_data.aoc_patch_info; + break; + default: + title_info = NULL; + break; + } + + if (title_info) + { + if (child_menu->id == MenuId_NCA) + { + updateNcaList(title_info); + + if (!g_ncaMenuElements || !g_ncaMenuElements[0]) + { + consolePrint("failed to generate nca list\n"); + error = true; + } + } + + if (!error) + { + title_info_count = titleGetCountFromInfoBlock(title_info); + title_info_idx = 1; + } + } else { + consolePrint("\nthe selected title doesn't have available %s data\n", \ + title_type == NcmContentMetaType_Application ? "base application" : \ + (title_type == NcmContentMetaType_Patch ? "update" : (title_type == NcmContentMetaType_AddOnContent ? "dlc" : "dlc update"))); + + error = true; + } + } + + if (!error) + { + child_menu->parent = cur_menu; + child_menu->selected = child_menu->scroll = 0; + + cur_menu = child_menu; + element_count = menuGetElementCount(cur_menu); + } else { + consolePrint("press any button to go back\n"); + consoleRefresh(); + utilsWaitForButtonPress(0); + } + } else + if (selected_element->task_func) + { + consoleClear(); + + /* Wait for gamecard (if needed). */ + if (((cur_menu->id >= MenuId_GameCard && cur_menu->id <= MenuId_HFS) || (title_info && title_info->storage_id == NcmStorageId_GameCard)) && !waitForGameCard()) + { + if (g_appletStatus) continue; + break; + } + + /* Wait for USB session (if needed). */ + if (useUsbHost() && !waitForUsb()) + { + if (g_appletStatus) continue; + break; + } + + /* Run task. */ + utilsSetLongRunningProcessState(true); + + if (selected_element->task_func(selected_element->userdata)) + { + if (!useUsbHost()) updateStorageList(); // update free space + } + + utilsSetLongRunningProcessState(false); + + /* Display prompt. */ + consolePrint("press any button to continue"); + utilsWaitForButtonPress(0); + } + } else + if (((btn_down & HidNpadButton_Down) || (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown))) && element_count) + { + cur_menu->selected++; + + if (!cur_menu->elements[cur_menu->selected]) + { + if (btn_down & HidNpadButton_Down) + { + cur_menu->selected = 0; + cur_menu->scroll = 0; + } else { + cur_menu->selected--; + } + } else + if (cur_menu->selected >= (cur_menu->scroll + (page_size / 2)) && element_count > (cur_menu->scroll + page_size)) + { + cur_menu->scroll++; + } + } else + if (((btn_down & HidNpadButton_Up) || (btn_held & (HidNpadButton_StickLUp | HidNpadButton_StickRUp))) && element_count) + { + cur_menu->selected--; + + if (cur_menu->selected == UINT32_MAX) + { + if (btn_down & HidNpadButton_Up) + { + cur_menu->selected = (element_count - 1); + cur_menu->scroll = (element_count >= page_size ? (element_count - page_size) : 0); + } else { + cur_menu->selected = 0; + } + } else + if (cur_menu->selected < (cur_menu->scroll + (page_size / 2)) && cur_menu->scroll > 0) + { + cur_menu->scroll--; + } + } else + if ((btn_down & (HidNpadButton_Right | HidNpadButton_StickLRight | HidNpadButton_StickRRight)) && selected_element_options) + { + selected_element_options->selected++; + if (!selected_element_options->options[selected_element_options->selected]) selected_element_options->selected--; + if (selected_element_options->setter_func) selected_element_options->setter_func(selected_element_options->selected); + } else + if ((btn_down & (HidNpadButton_Left | HidNpadButton_StickLLeft | HidNpadButton_StickRLeft)) && selected_element_options) + { + selected_element_options->selected--; + if (selected_element_options->selected == UINT32_MAX) selected_element_options->selected = 0; + if (selected_element_options->setter_func) selected_element_options->setter_func(selected_element_options->selected); + } else + if (btn_down & HidNpadButton_B) + { + if (!cur_menu->parent) break; + + if (cur_menu->id == MenuId_UserTitles) + { + app_metadata = NULL; + } else + if (cur_menu->id == MenuId_UserTitlesSubMenu) + { + titleFreeUserApplicationData(&user_app_data); + g_titleTypesMenuElements[0]->child_menu = g_titleTypesMenuElements[1]->child_menu = \ + g_titleTypesMenuElements[2]->child_menu = g_titleTypesMenuElements[3]->child_menu = NULL; + } else + if (cur_menu->id == MenuId_NSPTitleTypes || cur_menu->id == MenuId_TicketTitleTypes || cur_menu->id == MenuId_NCATitleTypes) + { + title_info = NULL; + title_info_idx = title_info_count = 0; + } else + if (cur_menu->id == MenuId_NSP) + { + g_nspMenuElements[0]->userdata = NULL; + } else + if (cur_menu->id == MenuId_Ticket) + { + g_ticketMenuElements[0]->userdata = NULL; + } else + if (cur_menu->id == MenuId_NCA) + { + freeNcaList(); + } + + cur_menu->selected = 0; + cur_menu->scroll = 0; + + cur_menu = cur_menu->parent; + element_count = menuGetElementCount(cur_menu); + } else + if ((btn_down & HidNpadButton_X) && g_umsDeviceCount) + { + for(u32 i = 0; i < g_umsDeviceCount; i++) usbHsFsUnmountDevice(&(g_umsDevices[i]), false); + updateStorageList(); + } else + if (((btn_down & (HidNpadButton_L)) || (btn_held & HidNpadButton_ZL)) && (cur_menu->id == MenuId_NSP || cur_menu->id == MenuId_Ticket || cur_menu->id == MenuId_NCA) && title_info->previous) + { + title_info = title_info->previous; + title_info_idx--; + + if (cur_menu->id == MenuId_NCA) + { + updateNcaList(title_info); + if (!g_ncaMenuElements || !g_ncaMenuElements[0]) + { + freeNcaList(); + consolePrint("\nfailed to generate nca list for newly selected title\npress any button to go back\n"); + consoleRefresh(); + utilsWaitForButtonPress(0); + + cur_menu->selected = 0; + cur_menu->scroll = 0; + + cur_menu = cur_menu->parent; + element_count = menuGetElementCount(cur_menu); + } + } + } else + if (((btn_down & (HidNpadButton_R)) || (btn_held & HidNpadButton_ZR)) && (cur_menu->id == MenuId_NSP || cur_menu->id == MenuId_Ticket || cur_menu->id == MenuId_NCA) && title_info->next) + { + title_info = title_info->next; + title_info_idx++; + + if (cur_menu->id == MenuId_NCA) + { + updateNcaList(title_info); + if (!g_ncaMenuElements || !g_ncaMenuElements[0]) + { + freeNcaList(); + consolePrint("\nfailed to generate nca list for newly selected title\npress any button to go back\n"); + consoleRefresh(); + utilsWaitForButtonPress(0); + + cur_menu->selected = 0; + cur_menu->scroll = 0; + + cur_menu = cur_menu->parent; + element_count = menuGetElementCount(cur_menu); + } + } + } + + if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp | HidNpadButton_ZL | HidNpadButton_ZR)) svcSleepThread(50000000); // 50 ms + } + +end: + titleFreeUserApplicationData(&user_app_data); + + freeNcaList(); + + freeTitleList(); + + freeStorageList(); + + utilsCloseResources(); + + consoleExit(NULL); + + return ret; +} + +static void utilsScanPads(void) +{ + padUpdate(&g_padState); +} + +static u64 utilsGetButtonsDown(void) +{ + return padGetButtonsDown(&g_padState); +} + +static u64 utilsGetButtonsHeld(void) +{ + return padGetButtons(&g_padState); +} + +static void utilsWaitForButtonPress(u64 flag) +{ + /* Don't consider stick movement as button inputs. */ + if (!flag) flag = ~(HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \ + HidNpadButton_StickRUp | HidNpadButton_StickRDown); + + consoleRefresh(); + + while(appletMainLoop()) + { + utilsScanPads(); + if (utilsGetButtonsDown() & flag) break; + } +} + +static void consolePrint(const char *text, ...) +{ + mutexLock(&g_conMutex); + va_list v; + va_start(v, text); + vfprintf(stdout, text, v); + va_end(v); + mutexUnlock(&g_conMutex); +} + +static void consoleRefresh(void) +{ + mutexLock(&g_conMutex); + fflush(stdout); + consoleUpdate(NULL); + mutexUnlock(&g_conMutex); +} + +static u32 menuGetElementCount(const Menu *menu) +{ + if (!menu || !menu->elements || !menu->elements[0]) return 0; + + u32 cnt; + for(cnt = 0; menu->elements[cnt]; cnt++); + return cnt; +} + +void freeStorageList(void) +{ + u32 elem_count = (2 + g_umsDeviceCount); // sd card, usb host, ums devices + + /* Free all previously allocated data. */ + if (g_storageOptions) + { + for(u32 i = 0; i < elem_count && g_storageOptions[i]; i++) + { + free(g_storageOptions[i]); + g_storageOptions[i] = NULL; + } + + free(g_storageOptions); + g_storageOptions = NULL; + } + + if (g_umsDevices) + { + free(g_umsDevices); + g_umsDevices = NULL; + } + + g_umsDeviceCount = 0; +} + +void updateStorageList(void) +{ + char **tmp = NULL; + u32 elem_count = 0, idx = 0; + + /* Free all previously allocated data. */ + freeStorageList(); + + /* Get UMS devices. */ + g_umsDevices = umsGetDevices(&g_umsDeviceCount); + elem_count = (2 + g_umsDeviceCount); // sd card, usb host, ums devices + + /* Reallocate buffer. */ + tmp = realloc(g_storageOptions, (elem_count + 1) * sizeof(char*)); // NULL terminator + + g_storageOptions = tmp; + tmp = NULL; + + memset(g_storageOptions, 0, (elem_count + 1) * sizeof(char*)); // NULL terminator + + /* Generate UMS device strings. */ + for(u32 i = 0; i < elem_count; i++) + { + u64 total = 0, free = 0; + char total_str[36] = {0}, free_str[32] = {0}; + + g_storageOptions[idx] = calloc(sizeof(char), 0x300); + if (!g_storageOptions[idx]) continue; + + if (i == 1) + { + sprintf(g_storageOptions[idx], "usb host (pc)"); + } else { + UsbHsFsDevice *ums_device = (i >= 2 ? &(g_umsDevices[i - 2]) : NULL); + + sprintf(total_str, "%s/", i == 0 ? DEVOPTAB_SDMC_DEVICE : ums_device->name); + utilsGetFileSystemStatsByPath(total_str, &total, &free); + utilsGenerateFormattedSizeString(total, total_str, sizeof(total_str)); + utilsGenerateFormattedSizeString(free, free_str, sizeof(free_str)); + + if (i == 0) + { + sprintf(g_storageOptions[idx], DEVOPTAB_SDMC_DEVICE " (%s / %s)", free_str, total_str); + } else { + if (ums_device->product_name[0]) + { + sprintf(g_storageOptions[idx], "%s (%s, LUN %u, FS #%u, %s)", ums_device->name, ums_device->product_name, ums_device->lun, ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(ums_device->fs_type)); + } else { + sprintf(g_storageOptions[idx], "%s (LUN %u, FS #%u, %s)", ums_device->name, ums_device->lun, ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(ums_device->fs_type)); + } + + sprintf(g_storageOptions[idx] + strlen(g_storageOptions[idx]), " (%s / %s)", free_str, total_str); + } + } + + idx++; + } + + /* Update storage menu element options. */ + if (g_storageMenuElementOption.selected >= elem_count) + { + g_storageMenuElementOption.selected = 0; + setOutputStorageOption(0); + } + + g_storageMenuElementOption.options = g_storageOptions; +} + +void freeTitleList(void) +{ + /* Free all previously allocated data. */ + if (g_userTitlesMenuElements) + { + for(u32 i = 0; g_userTitlesMenuElements[i] != NULL; i++) free(g_userTitlesMenuElements[i]); + + free(g_userTitlesMenuElements); + g_userTitlesMenuElements = NULL; + } + + g_userTitlesMenu.scroll = 0; + g_userTitlesMenu.selected = 0; + g_userTitlesMenu.elements = NULL; +} + +void updateTitleList(void) +{ + u32 app_count = 0, idx = 0; + TitleApplicationMetadata **app_metadata = NULL; + MenuElement **tmp = NULL; + + /* Free all previously allocated data. */ + freeTitleList(); + + /* Get application metadata entries. */ + app_metadata = titleGetApplicationMetadataEntries(false, &app_count); + if (!app_metadata || !app_count) goto end; + + /* Reallocate buffer. */ + tmp = realloc(g_userTitlesMenuElements, (app_count + 1) * sizeof(MenuElement*)); // NULL terminator + + g_userTitlesMenuElements = tmp; + tmp = NULL; + + memset(g_userTitlesMenuElements, 0, (app_count + 1) * sizeof(MenuElement*)); // NULL terminator + + /* Generate menu elements. */ + for(u32 i = 0; i < app_count; i++) + { + TitleApplicationMetadata *cur_app_metadata = app_metadata[i]; + + g_userTitlesMenuElements[idx] = calloc(1, sizeof(MenuElement)); + if (!g_userTitlesMenuElements[idx]) continue; + + g_userTitlesMenuElements[idx]->str = cur_app_metadata->lang_entry.name; + g_userTitlesMenuElements[idx]->child_menu = &g_userTitlesSubMenu; + g_userTitlesMenuElements[idx]->userdata = cur_app_metadata; + + idx++; + } + + g_userTitlesMenu.elements = g_userTitlesMenuElements; + +end: + if (app_metadata) free(app_metadata); +} + +void freeNcaList(void) +{ + /* Free all previously allocated data. */ + if (g_ncaMenuElements) + { + for(u32 i = 0; g_ncaMenuElements[i] != NULL; i++) + { + if (g_ncaMenuElements[i]->str) free(g_ncaMenuElements[i]->str); + if (g_ncaMenuElements[i]->userdata) free(g_ncaMenuElements[i]->userdata); + free(g_ncaMenuElements[i]); + } + + free(g_ncaMenuElements); + g_ncaMenuElements = NULL; + } + + g_ncaMenu.scroll = 0; + g_ncaMenu.selected = 0; + g_ncaMenu.elements = NULL; +} + +void updateNcaList(TitleInfo *title_info) +{ + u32 content_count = title_info->content_count, idx = 0; + NcmContentInfo *content_infos = title_info->content_infos; + MenuElement **tmp = NULL; + char nca_id_str[0x21] = {0}; + + /* Free all previously allocated data. */ + freeNcaList(); + + /* Reallocate buffer. */ + tmp = realloc(g_ncaMenuElements, (content_count + 1) * sizeof(MenuElement*)); // NULL terminator + + g_ncaMenuElements = tmp; + tmp = NULL; + + memset(g_ncaMenuElements, 0, (content_count + 1) * sizeof(MenuElement*)); // NULL terminator + + /* Generate menu elements. */ + for(u32 i = 0; i < content_count; i++) + { + NcmContentInfo *cur_content_info = &(content_infos[i]); + char *nca_info_str = NULL, nca_size_str[16] = {0}; + u64 nca_size = 0; + NcaUserData *nca_user_data = NULL; + + g_ncaMenuElements[idx] = calloc(1, sizeof(MenuElement)); + if (!g_ncaMenuElements[idx]) continue; + + nca_info_str = calloc(128, sizeof(char)); + nca_user_data = calloc(1, sizeof(NcaUserData)); + + if (!nca_info_str || !nca_user_data) + { + if (nca_info_str) free(nca_info_str); + if (nca_user_data) free(nca_user_data); + continue; + } + + utilsGenerateHexStringFromData(nca_id_str, sizeof(nca_id_str), cur_content_info->content_id.c, sizeof(cur_content_info->content_id.c), false); + + ncmContentInfoSizeToU64(cur_content_info, &nca_size); + utilsGenerateFormattedSizeString((double)nca_size, nca_size_str, sizeof(nca_size_str)); + + sprintf(nca_info_str, "%s #%u: %s (%s)", titleGetNcmContentTypeName(cur_content_info->content_type), cur_content_info->id_offset, nca_id_str, nca_size_str); + + nca_user_data->title_info = title_info; + nca_user_data->content_idx = i; + + g_ncaMenuElements[idx]->str = nca_info_str; + //g_ncaMenuElements[idx]->child_menu = NULL; + g_ncaMenuElements[idx]->task_func = &saveNintendoContentArchive; + g_ncaMenuElements[idx]->userdata = nca_user_data; + + idx++; + } + + g_ncaMenu.elements = g_ncaMenuElements; +} + +NX_INLINE bool useUsbHost(void) +{ + return (g_storageMenuElementOption.selected == 1); +} + +static bool waitForGameCard(void) +{ + consolePrint("waiting for gamecard... "); + consoleRefresh(); + + time_t start = time(NULL); + u8 status = GameCardStatus_NotInserted; + + while((g_appletStatus = appletMainLoop())) + { + if ((status = gamecardGetStatus()) > GameCardStatus_Processing) break; + + time_t now = time(NULL); + time_t diff = (now - start); + + if (diff >= WAIT_TIME_LIMIT) break; + + consolePrint("%lu ", diff); + consoleRefresh(); + + utilsSleep(1); + } + + consolePrint("\n"); + consoleRefresh(); + + if (!g_appletStatus || status == GameCardStatus_NotInserted) return false; + + switch(status) + { + case GameCardStatus_NoGameCardPatchEnabled: + consolePrint("\"nogc\" patch enabled, please disable it and reboot your console\n"); + break; + case GameCardStatus_LotusAsicFirmwareUpdateRequired: + consolePrint("gamecard controller firmware update required, please update your console\n"); + break; + case GameCardStatus_InsertedAndInfoNotLoaded: + consolePrint("unexpected I/O error occurred, please check the logfile\n"); + break; + default: + break; + } + + if (status != GameCardStatus_InsertedAndInfoLoaded) + { + consolePrint("press any button\n"); + utilsWaitForButtonPress(0); + return false; + } + + return true; +} + +static bool waitForUsb(void) +{ + consolePrint("waiting for usb session... "); + consoleRefresh(); + + time_t start = time(NULL); + u8 usb_host_speed = UsbHostSpeed_None; + + while((g_appletStatus = appletMainLoop())) + { + if ((usb_host_speed = usbIsReady()) != UsbHostSpeed_None) break; + + time_t now = time(NULL); + time_t diff = (now - start); + + if (diff >= WAIT_TIME_LIMIT) break; + + consolePrint("%lu ", diff); + consoleRefresh(); + + utilsSleep(1); + } + + consolePrint("\n"); + if (usb_host_speed != UsbHostSpeed_None) consolePrint("usb speed: %u.0\n", usb_host_speed); + consoleRefresh(); + + return (g_appletStatus && usb_host_speed != UsbHostSpeed_None); +} + +static bool saveFileData(const char *filepath, void *data, size_t data_size) +{ + if (!filepath || !*filepath || !data || !data_size) + { + consolePrint("invalid parameters to save file data!\n"); + return false; + } + + if (useUsbHost()) + { + if (!usbSendFileProperties(data_size, filepath)) + { + consolePrint("failed to send file properties for \"%s\"!\n", filepath); + return false; + } + + if (!usbSendFileData(data, data_size)) + { + consolePrint("failed to send file data for \"%s\"!\n", filepath); + return false; + } + } else { + utilsCreateDirectoryTree(filepath, false); + + FILE *fp = fopen(filepath, "wb"); + if (!fp) + { + consolePrint("failed to open \"%s\" for writing!\n", filepath); + return false; + } + + ftruncate(fileno(fp), (off_t)data_size); + size_t ret = fwrite(data, 1, data_size, fp); + fclose(fp); + + if (g_storageMenuElementOption.selected == 0) utilsCommitSdCardFileSystemChanges(); + + if (ret != data_size) + { + consolePrint("failed to write 0x%lX byte(s) to \"%s\"! (%d)\n", data_size, filepath, errno); + remove(filepath); + } + } + + return true; +} + +static char *generateOutputGameCardFileName(const char *subdir, const char *extension, bool use_nacp_name) +{ + char *filename = NULL, *prefix = NULL, *output = NULL; + u32 dev_idx = g_storageMenuElementOption.selected; + + if ((subdir && !*subdir) || !extension || !*extension || (use_nacp_name && !(filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, dev_idx > 0 ? TitleFileNameIllegalCharReplaceType_IllegalFsChars : TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly)))) + { + consolePrint("failed to generate gamecard filename!\n"); + goto end; + } + + prefix = calloc(sizeof(char), FS_MAX_PATH); + if (!prefix) + { + consolePrint("failed to generate prefix!\n"); + goto end; + } + + if (dev_idx == 1) + { + if (subdir) sprintf(prefix, "/%s", subdir); + } else { + sprintf(prefix, "%s/" OUTDIR, dev_idx == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[dev_idx - 2].name); + + if (subdir) + { + if (subdir[0] != '/') strcat(prefix, "/"); + strcat(prefix, subdir); + } + } + + output = (use_nacp_name ? utilsGeneratePath(prefix, filename, extension) : utilsGeneratePath(prefix, extension, NULL)); + if (!output) consolePrint("failed to generate output filename!\n"); + +end: + if (prefix) free(prefix); + if (filename) free(filename); + + return output; +} + +static char *generateOutputTitleFileName(TitleInfo *title_info, const char *subdir, const char *extension) +{ + char *filename = NULL, *prefix = NULL, *output = NULL; + u32 dev_idx = g_storageMenuElementOption.selected; + + if (!title_info || (subdir && !*subdir) || !extension || !*extension || !(filename = titleGenerateFileName(title_info, TitleNamingConvention_Full, dev_idx > 0 ? TitleFileNameIllegalCharReplaceType_IllegalFsChars : TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly))) + { + consolePrint("failed to generate title filename!\n"); + goto end; + } + + prefix = calloc(sizeof(char), FS_MAX_PATH); + if (!prefix) + { + consolePrint("failed to generate prefix!\n"); + goto end; + } + + if (dev_idx == 1) + { + if (subdir) sprintf(prefix, "/%s", subdir); + } else { + sprintf(prefix, "%s/" OUTDIR, dev_idx == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[dev_idx - 2].name); + + if (subdir) + { + if (subdir[0] != '/') strcat(prefix, "/"); + strcat(prefix, subdir); + } + } + + output = utilsGeneratePath(prefix, filename, extension); + if (!output) consolePrint("failed to generate output filename!\n"); + +end: + if (prefix) free(prefix); + if (filename) free(filename); + + return output; +} + + + + + + + + + + + + + +static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out) +{ + if (!out) + { + consolePrint("invalid parameters to dump gamecard security information!\n"); + return false; + } + + if (!gamecardGetSecurityInformation(out)) + { + consolePrint("failed to get gamecard security information\n"); + return false; + } + + consolePrint("get gamecard security information ok\n"); + return true; +} + +static bool saveGameCardImage(void *userdata) +{ + (void)userdata; + + u64 gc_size = 0, free_space = 0; + + u32 key_area_crc = 0; + GameCardKeyArea gc_key_area = {0}; + GameCardSecurityInformation gc_security_information = {0}; + + XciThreadData xci_thread_data = {0}; + SharedThreadData *shared_thread_data = &(xci_thread_data.shared_thread_data); + + char *filename = NULL; + u32 dev_idx = g_storageMenuElementOption.selected; + + bool prepend_key_area = (bool)getGameCardPrependKeyAreaOption(); + bool keep_certificate = (bool)getGameCardKeepCertificateOption(); + bool trim_dump = (bool)getGameCardTrimDumpOption(); + bool calculate_checksum = (bool)getGameCardCalculateChecksumOption(); + + bool success = false; + + consolePrint("gamecard image dump\nprepend key area: %s | keep certificate: %s | trim dump: %s | calculate checksum: %s\n\n", prepend_key_area ? "yes" : "no", keep_certificate ? "yes" : "no", trim_dump ? "yes" : "no", calculate_checksum ? "yes" : "no"); + + if ((!trim_dump && !gamecardGetTotalSize(&gc_size)) || (trim_dump && !gamecardGetTrimmedSize(&gc_size)) || !gc_size) + { + consolePrint("failed to get gamecard size!\n"); + goto end; + } + + shared_thread_data->total_size = gc_size; + + consolePrint("gamecard size: 0x%lX\n", gc_size); + + if (prepend_key_area) + { + gc_size += sizeof(GameCardKeyArea); + + if (!dumpGameCardSecurityInformation(&gc_security_information)) goto end; + + memcpy(&(gc_key_area.initial_data), &(gc_security_information.initial_data), sizeof(GameCardInitialData)); + + if (calculate_checksum) + { + key_area_crc = crc32Calculate(&gc_key_area, sizeof(GameCardKeyArea)); + xci_thread_data.full_xci_crc = key_area_crc; + } + + consolePrint("gamecard size (with key area): 0x%lX\n", gc_size); + } + + snprintf(path, MAX_ELEMENTS(path), " (%s) (%s) (%s).xci", prepend_key_area ? "keyarea" : "keyarealess", keep_certificate ? "cert" : "certless", trim_dump ? "trimmed" : "untrimmed"); + filename = generateOutputGameCardFileName("Gamecard", path, true); + if (!filename) goto end; + + if (dev_idx == 1) + { + if (!usbSendFileProperties(gc_size, filename)) + { + consolePrint("failed to send file properties for \"%s\"!\n", filename); + goto end; + } + + if (prepend_key_area && !usbSendFileData(&gc_key_area, sizeof(GameCardKeyArea))) + { + consolePrint("failed to send gamecard key area data!\n"); + goto end; + } + } else { + if (!utilsGetFileSystemStatsByPath(filename, NULL, &free_space)) + { + consolePrint("failed to retrieve free space from selected device\n"); + goto end; + } + + if (gc_size >= free_space) + { + consolePrint("dump size exceeds free space\n"); + goto end; + } + + utilsCreateDirectoryTree(filename, false); + + if (dev_idx == 0) + { + if (gc_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 && gc_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; + } + + ftruncate(fileno(shared_thread_data->fp), (off_t)shared_thread_data->total_size); + + if (prepend_key_area && fwrite(&gc_key_area, 1, sizeof(GameCardKeyArea), shared_thread_data->fp) != sizeof(GameCardKeyArea)) + { + consolePrint("failed to write gamecard key area data!\n"); + goto end; + } + } + + consoleRefresh(); + + success = spanDumpThreads(xciReadThreadFunc, genericWriteThreadFunc, &xci_thread_data); + + if (success) + { + consolePrint("successfully saved xci as \"%s\"\n", filename); + + if (calculate_checksum) + { + if (prepend_key_area) consolePrint("key area crc: %08X | ", key_area_crc); + consolePrint("xci crc: %08X", xci_thread_data.xci_crc); + if (prepend_key_area) consolePrint(" | xci crc (with key area): %08X", xci_thread_data.full_xci_crc); + consolePrint("\n"); + } + + 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 saveGameCardHeader(void *userdata) +{ + (void)userdata; + + GameCardHeader gc_header = {0}; + bool success = false; + u32 crc = 0; + char *filename = NULL; + + if (!gamecardGetHeader(&gc_header)) + { + consolePrint("failed to get gamecard header\n"); + goto end; + } + + consolePrint("get gamecard header ok\n"); + + crc = crc32Calculate(&gc_header, sizeof(GameCardHeader)); + snprintf(path, MAX_ELEMENTS(path), " (Header) (%08X).bin", crc); + + filename = generateOutputGameCardFileName("Gamecard", path, true); + if (!filename) goto end; + + if (!saveFileData(filename, &gc_header, sizeof(GameCardHeader))) goto end; + + consolePrint("successfully saved header as \"%s\"\n", filename); + success = true; + +end: + if (filename) free(filename); + + return success; +} + +static bool saveGameCardCardInfo(void *userdata) +{ + (void)userdata; + + GameCardInfo gc_cardinfo = {0}; + bool success = false; + u32 crc = 0; + char *filename = NULL; + + if (!gamecardGetDecryptedCardInfoArea(&gc_cardinfo)) + { + consolePrint("failed to get gamecard cardinfo\n"); + goto end; + } + + consolePrint("get gamecard cardinfo ok\n"); + + crc = crc32Calculate(&gc_cardinfo, sizeof(GameCardInfo)); + snprintf(path, MAX_ELEMENTS(path), " (CardInfo) (%08X).bin", crc); + + filename = generateOutputGameCardFileName("Gamecard", path, true); + if (!filename) goto end; + + if (!saveFileData(filename, &gc_cardinfo, sizeof(GameCardInfo))) goto end; + + consolePrint("successfully saved cardinfo dump as \"%s\"\n", filename); + success = true; + +end: + if (filename) free(filename); + + return success; +} + +static bool saveGameCardCertificate(void *userdata) +{ + (void)userdata; + + FsGameCardCertificate gc_cert = {0}; + bool success = false; + u32 crc = 0; + char *filename = NULL; + + if (!gamecardGetCertificate(&gc_cert)) + { + consolePrint("failed to get gamecard certificate\n"); + goto end; + } + + consolePrint("get gamecard certificate ok\n"); + + crc = crc32Calculate(&gc_cert, sizeof(FsGameCardCertificate)); + snprintf(path, MAX_ELEMENTS(path), " (Certificate) (%08X).bin", crc); + + filename = generateOutputGameCardFileName("Gamecard", path, true); + if (!filename) goto end; + + if (!saveFileData(filename, &gc_cert, sizeof(FsGameCardCertificate))) goto end; + + consolePrint("successfully saved certificate as \"%s\"\n", filename); + success = true; + +end: + if (filename) free(filename); + + return success; +} + +static bool saveGameCardInitialData(void *userdata) +{ + (void)userdata; + + GameCardSecurityInformation gc_security_information = {0}; + bool success = false; + u32 crc = 0; + char *filename = NULL; + + if (!dumpGameCardSecurityInformation(&gc_security_information)) goto end; + + crc = crc32Calculate(&(gc_security_information.initial_data), sizeof(GameCardInitialData)); + snprintf(path, MAX_ELEMENTS(path), " (Initial Data) (%08X).bin", crc); + + filename = generateOutputGameCardFileName("Gamecard", path, true); + if (!filename) goto end; + + if (!saveFileData(filename, &(gc_security_information.initial_data), sizeof(GameCardInitialData))) goto end; + + consolePrint("successfully saved initial data as \"%s\"\n", filename); + success = true; + +end: + if (filename) free(filename); + + return success; +} + +static bool saveGameCardSpecificData(void *userdata) +{ + (void)userdata; + + GameCardSecurityInformation gc_security_information = {0}; + bool success = false; + u32 crc = 0; + char *filename = NULL; + + if (!dumpGameCardSecurityInformation(&gc_security_information)) goto end; + + crc = crc32Calculate(&(gc_security_information.specific_data), sizeof(GameCardSpecificData)); + snprintf(path, MAX_ELEMENTS(path), " (Specific Data) (%08X).bin", crc); + + filename = generateOutputGameCardFileName("Gamecard", path, true); + if (!filename) goto end; + + if (!saveFileData(filename, &(gc_security_information.specific_data), sizeof(GameCardSpecificData))) goto end; + + consolePrint("successfully saved specific data as \"%s\"\n", filename); + success = true; + +end: + if (filename) free(filename); + + return success; +} + +static bool saveGameCardIdSet(void *userdata) +{ + (void)userdata; + + FsGameCardIdSet id_set = {0}; + bool success = false; + u32 crc = 0; + char *filename = NULL; + + if (!gamecardGetIdSet(&id_set)) + { + consolePrint("failed to get gamecard id set\n"); + goto end; + } + + crc = crc32Calculate(&id_set, sizeof(FsGameCardIdSet)); + snprintf(path, MAX_ELEMENTS(path), " (Card ID Set) (%08X).bin", crc); + + filename = generateOutputGameCardFileName("Gamecard", path, true); + if (!filename) goto end; + + if (!saveFileData(filename, &id_set, sizeof(FsGameCardIdSet))) goto end; + + consolePrint("successfully saved gamecard id set as \"%s\"\n", filename); + success = true; + +end: + if (filename) free(filename); + + return success; +} + +static bool saveGameCardHfsPartition(void *userdata) +{ + u32 hfs_partition_type = (userdata ? *((u32*)userdata) : HashFileSystemPartitionType_None); + bool write_raw_hfs_partition = (bool)getGameCardWriteRawHfsPartitionOption(); + HashFileSystemContext hfs_ctx = {0}; + + bool success = false; + + if (hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type > HashFileSystemPartitionType_Secure) + { + consolePrint("invalid hfs partition type! (%u)\n", hfs_partition_type); + goto end; + } + + if (!gamecardGetHashFileSystemContext(hfs_partition_type, &hfs_ctx)) + { + consolePrint("get hfs ctx failed! this partition type may not exist within the inserted gamecard\n"); + goto end; + } + + success = (write_raw_hfs_partition ? saveGameCardRawHfsPartition(&hfs_ctx) : saveGameCardExtractedHfsPartition(&hfs_ctx)); + +end: + hfsFreeContext(&hfs_ctx); + + return success; +} + +static bool saveGameCardRawHfsPartition(HashFileSystemContext *hfs_ctx) +{ + u64 free_space = 0; + + HfsThreadData hfs_thread_data = {0}; + SharedThreadData *shared_thread_data = &(hfs_thread_data.shared_thread_data); + + char *filename = NULL; + u32 dev_idx = g_storageMenuElementOption.selected; + + bool success = false; + + hfs_thread_data.hfs_ctx = hfs_ctx; + shared_thread_data->total_size = hfs_ctx->size; + + consolePrint("raw %s hfs partition size: 0x%lX\n", hfs_ctx->name, hfs_ctx->size); + + snprintf(path, MAX_ELEMENTS(path), "/%s.hfs0", hfs_ctx->name); + filename = generateOutputGameCardFileName("HFS/Raw", path, true); + 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; + } + + ftruncate(fileno(shared_thread_data->fp), (off_t)shared_thread_data->total_size); + } + + consoleRefresh(); + + success = spanDumpThreads(rawHfsReadThreadFunc, genericWriteThreadFunc, &hfs_thread_data); + + if (success) + { + consolePrint("successfully saved raw hfs partition 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 saveGameCardExtractedHfsPartition(HashFileSystemContext *hfs_ctx) +{ + u64 data_size = 0; + + HfsThreadData hfs_thread_data = {0}; + SharedThreadData *shared_thread_data = &(hfs_thread_data.shared_thread_data); + + bool success = false; + + if (!hfsGetTotalDataSize(hfs_ctx, &data_size)) + { + consolePrint("failed to calculate extracted %s hfs partition size!\n", hfs_ctx->name); + goto end; + } + + if (!data_size) + { + consolePrint("%s hfs partition is empty!\n", hfs_ctx->name); + goto end; + } + + hfs_thread_data.hfs_ctx = hfs_ctx; + shared_thread_data->total_size = data_size; + + consolePrint("extracted %s hfs partition size: 0x%lX\n", hfs_ctx->name, data_size); + consoleRefresh(); + + success = spanDumpThreads(extractedHfsReadThreadFunc, genericWriteThreadFunc, &hfs_thread_data); + +end: + return success; +} + +static bool saveConsoleLafwBlob(void *userdata) +{ + (void)userdata; + + u64 lafw_version = 0; + LotusAsicFirmwareBlob lafw_blob = {0}; + bool success = false; + u32 crc = 0; + char *filename = NULL; + const char *fw_type_str = NULL, *dev_type_str = NULL; + + if (!gamecardGetLotusAsicFirmwareBlob(&lafw_blob, &lafw_version)) + { + consolePrint("failed to get console lafw blob\n"); + goto end; + } + + fw_type_str = gamecardGetLafwTypeString(lafw_blob.fw_type); + if (!fw_type_str) fw_type_str = "Unknown"; + + dev_type_str = gamecardGetLafwDeviceTypeString(lafw_blob.device_type); + if (!dev_type_str) dev_type_str = "Unknown"; + + consolePrint("get console lafw blob ok\n"); + + crc = crc32Calculate(&lafw_blob, sizeof(LotusAsicFirmwareBlob)); + snprintf(path, MAX_ELEMENTS(path), "LAFW (%s) (%s) (v%lu) (%08X).bin", fw_type_str, dev_type_str, lafw_version, crc); + + filename = generateOutputGameCardFileName(NULL, path, false); + if (!filename) goto end; + + if (!saveFileData(filename, &lafw_blob, sizeof(LotusAsicFirmwareBlob))) goto end; + + consolePrint("successfully saved lafw blob as \"%s\"\n", filename); + success = true; + +end: + if (filename) free(filename); + + return success; +} + +static bool saveNintendoSubmissionPackage(void *userdata) +{ + if (!userdata) return false; + + TitleInfo *title_info = (TitleInfo*)userdata; + TitleApplicationMetadata *app_metadata = title_info->app_metadata; + + NspThreadData nsp_thread_data = {0}; + Thread dump_thread = {0}; + + time_t start = 0, btn_cancel_start_tmr = 0, btn_cancel_end_tmr = 0; + bool btn_cancel_cur_state = false, btn_cancel_prev_state = false, success = false; + + u64 prev_size = 0; + u8 prev_time = 0, percent = 0; + + consolePrint("%s info:\n\n", title_info->meta_key.type == NcmContentMetaType_Application ? "base application" : \ + (title_info->meta_key.type == NcmContentMetaType_Patch ? "update" : \ + (title_info->meta_key.type == NcmContentMetaType_AddOnContent ? "dlc" : "dlc update"))); + + if (app_metadata) + { + consolePrint("name: %s\n", app_metadata->lang_entry.name); + consolePrint("publisher: %s\n", app_metadata->lang_entry.author); + } + + consolePrint("source storage: %s\n", titleGetNcmStorageIdName(title_info->storage_id)); + consolePrint("title id: %016lX\n", title_info->meta_key.id); + consolePrint("version: %u (%u.%u.%u-%u.%u)\n", title_info->version.value, title_info->version.system_version.major, title_info->version.system_version.minor, \ + title_info->version.system_version.micro, title_info->version.system_version.major_relstep, \ + title_info->version.system_version.minor_relstep); + consolePrint("content count: %u\n", title_info->content_count); + consolePrint("size: %s\n", title_info->size_str); + consolePrint("______________________________\n\n"); + + consoleRefresh(); + + /* Create dump thread. */ + nsp_thread_data.data = title_info; + utilsCreateThread(&dump_thread, nspThreadFunc, &nsp_thread_data, 2); + + /* Wait until the background thread calculates the NSP size. */ + while(!nsp_thread_data.total_size && !nsp_thread_data.error) svcSleepThread(10000000); // 10 ms + + if (nsp_thread_data.error) + { + utilsJoinThread(&dump_thread); + return false; + } + + /* Start dump. */ + start = time(NULL); + + while(nsp_thread_data.data_written < nsp_thread_data.total_size) + { + g_appletStatus = appletMainLoop(); + if (!g_appletStatus) + { + mutexLock(&g_fileMutex); + nsp_thread_data.transfer_cancelled = true; + mutexUnlock(&g_fileMutex); + } + + if (nsp_thread_data.error || nsp_thread_data.transfer_cancelled) break; + + struct tm ts = {0}; + time_t now = time(NULL); + localtime_r(&now, &ts); + + size_t size = nsp_thread_data.data_written; + + utilsScanPads(); + btn_cancel_cur_state = (utilsGetButtonsHeld() & HidNpadButton_B); + + if (btn_cancel_cur_state && btn_cancel_cur_state != btn_cancel_prev_state) + { + btn_cancel_start_tmr = now; + } else + if (btn_cancel_cur_state && btn_cancel_cur_state == btn_cancel_prev_state) + { + btn_cancel_end_tmr = now; + if ((btn_cancel_end_tmr - btn_cancel_start_tmr) >= 3) + { + mutexLock(&g_fileMutex); + nsp_thread_data.transfer_cancelled = true; + mutexUnlock(&g_fileMutex); + break; + } + } else { + btn_cancel_start_tmr = btn_cancel_end_tmr = 0; + } + + btn_cancel_prev_state = btn_cancel_cur_state; + + if (prev_time == ts.tm_sec || prev_size == size) continue; + + percent = (u8)((size * 100) / nsp_thread_data.total_size); + + prev_time = ts.tm_sec; + prev_size = size; + + consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, nsp_thread_data.total_size, percent, (now - start)); + consoleRefresh(); + } + + consolePrint("\nwaiting for thread to join\n"); + consoleRefresh(); + + utilsJoinThread(&dump_thread); + consolePrint("dump_thread done: %lu\n", time(NULL)); + + if (nsp_thread_data.error) + { + consolePrint("i/o error\n"); + } else + if (nsp_thread_data.transfer_cancelled) + { + consolePrint("process cancelled\n"); + } else { + start = (time(NULL) - start); + consolePrint("process completed in %lu seconds\n", start); + success = true; + } + + consoleRefresh(); + + return success; +} + +static bool saveTicket(void *userdata) +{ + TitleInfo *title_info = (TitleInfo*)userdata; + + u8 content_type = 0; + NcmContentInfo *content_info = NULL; + NcaContext *nca_ctx = NULL; + + Ticket tik = {0}; + TikCommonBlock *tik_common_block = NULL; + char enc_titlekey_str[33] = {0}; + + u32 crc = 0; + char *filename = NULL; + + bool remove_console_data = (bool)getTicketRemoveConsoleDataOption(); + bool success = false; + + if (!title_info || title_info->meta_key.type < NcmContentMetaType_Application || title_info->meta_key.type == NcmContentMetaType_Delta || \ + title_info->meta_key.type > NcmContentMetaType_DataPatch) + { + consolePrint("invalid title info object\n"); + return false; + } + + /* Get a NcmContentInfo entry for a potential NCA with a rights ID. */ + content_type = ((title_info->meta_key.type == NcmContentMetaType_Application || title_info->meta_key.type == NcmContentMetaType_Patch) ? NcmContentType_Program : NcmContentType_Data); + content_info = titleGetContentInfoByTypeAndIdOffset(title_info, content_type, 0); + if (!content_info) + { + consolePrint("content info entry with type 0x%X unavailable\n", content_type); + return false; + } + + /* Allocate buffer for NCA context. */ + if (!(nca_ctx = calloc(1, sizeof(NcaContext)))) + { + consolePrint("nca ctx calloc failed\n"); + goto end; + } + + /* Initialize NCA context. */ + if (!ncaInitializeContext(nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ + content_info, title_info->version.value, &tik)) + { + consolePrint("nca initialize ctx failed\n"); + goto end; + } + + /* Check if a ticket was retrieved. */ + if (!nca_ctx->rights_id_available) + { + consolePrint("rights id unavailable in target title -- this title doesn't use titlekey crypto\nthere's no ticket to be retrieved\n"); + goto end; + } + + if (!tik.size) + { + consolePrint("failed to retrieve ticket (unavailable?)\ntry launching nxdumptool while overriding the title you wish to dump a ticket from\n"); + goto end; + } + + /* Retrieve ticket common block. */ + if (!(tik_common_block = tikGetCommonBlock(tik.data))) + { + consolePrint("failed to get tik common block\n"); + goto end; + } + + /* Remove console-specific data, if needed. */ + if (remove_console_data && tik_common_block->titlekey_type == TikTitleKeyType_Personalized && !tikConvertPersonalizedTicketToCommonTicket(&tik, NULL, NULL)) + { + consolePrint("failed to convert personalized ticket to common ticket\n"); + goto end; + } + + /* Save ticket. */ + crc = crc32Calculate(tik.data, tik.size); + snprintf(path, MAX_ELEMENTS(path), " (%08X).tik", crc); + + filename = generateOutputTitleFileName(title_info, "Ticket", path); + if (!filename) goto end; + + if (!saveFileData(filename, tik.data, tik.size)) goto end; + + utilsGenerateHexStringFromData(enc_titlekey_str, MAX_ELEMENTS(enc_titlekey_str), tik.enc_titlekey, sizeof(tik.enc_titlekey), false); + + consolePrint("rights id: %s\n", tik.rights_id_str); + consolePrint("titlekey: %s\n\n", enc_titlekey_str); + + consolePrint("successfully saved ticket as \"%s\"\n", filename); + success = true; + +end: + if (filename) free(filename); + + if (nca_ctx) free(nca_ctx); + + return success; +} + +static bool saveNintendoContentArchive(void *userdata) +{ + if (!userdata) return false; + + NcaUserData *nca_user_data = (NcaUserData*)userdata; + TitleInfo *title_info = nca_user_data->title_info; + NcmContentInfo *content_info = &(title_info->content_infos[nca_user_data->content_idx]); + + NcaThreadData nca_thread_data = {0}; + SharedThreadData *shared_thread_data = &(nca_thread_data.shared_thread_data); + + u64 free_space = 0; + char *filename = NULL; + u32 dev_idx = g_storageMenuElementOption.selected; + + bool success = false; + + /* Allocate buffer for NCA context. */ + if (!(nca_thread_data.nca_ctx = calloc(1, sizeof(NcaContext)))) + { + consolePrint("nca ctx calloc failed\n"); + goto end; + } + + /* Initialize NCA context. */ + if (!ncaInitializeContext(nca_thread_data.nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ + content_info, title_info->version.value, NULL)) + { + consolePrint("nca initialize ctx failed\n"); + goto end; + } + + shared_thread_data->total_size = nca_thread_data.nca_ctx->content_size; + + consolePrint("nca size: 0x%lX\n", shared_thread_data->total_size); + + snprintf(path, MAX_ELEMENTS(path), "/%s.%s", nca_thread_data.nca_ctx->content_id_str, content_info->content_type == NcmContentType_Meta ? "cnmt.nca" : "nca"); + filename = generateOutputTitleFileName(title_info, "NCA/User", 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; + } + + ftruncate(fileno(shared_thread_data->fp), (off_t)shared_thread_data->total_size); + } + + consoleRefresh(); + + success = spanDumpThreads(ncaReadThreadFunc, genericWriteThreadFunc, &nca_thread_data); + + if (success) + { + consolePrint("successfully saved nca 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); + + if (nca_thread_data.nca_ctx) free(nca_thread_data.nca_ctx); + + 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 ? &(xci_thread_data->shared_thread_data) : NULL); + + buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + + if (!xci_thread_data || !shared_thread_data || !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 ? &(hfs_thread_data->shared_thread_data) : NULL); + HashFileSystemContext *hfs_ctx = (hfs_thread_data ? hfs_thread_data->hfs_ctx : NULL); + + buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + + if (!hfs_thread_data || !shared_thread_data || !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 ? &(hfs_thread_data->shared_thread_data) : NULL); + + HashFileSystemContext *hfs_ctx = (hfs_thread_data ? hfs_thread_data->hfs_ctx : NULL); + 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 (!hfs_thread_data || !shared_thread_data || !shared_thread_data->total_size || !hfs_ctx || !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; + goto end; + } + + if (shared_thread_data->total_size >= free_space) + { + consolePrint("dump size exceeds free space\n"); + shared_thread_data->read_error = true; + 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; + 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. */ + 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); + } + + 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); + + 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 ? &(nca_thread_data->shared_thread_data) : NULL); + NcaContext *nca_ctx = (nca_thread_data ? nca_thread_data->nca_ctx : NULL); + + buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + + if (!nca_thread_data || !shared_thread_data || !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); + + /* 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 = !ncaReadContentFile(nca_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 genericWriteThreadFunc(void *arg) +{ + SharedThreadData *shared_thread_data = (SharedThreadData*)arg; // UB but we don't care + if (!shared_thread_data) + { + shared_thread_data->write_error = true; + goto end; + } + + while(shared_thread_data->data_written < shared_thread_data->total_size) + { + /* Wait until the current file data chunk has been read */ + mutexLock(&g_fileMutex); + + if (!shared_thread_data->data_size && !shared_thread_data->read_error) condvarWait(&g_writeCondvar, &g_fileMutex); + + if (shared_thread_data->read_error || shared_thread_data->transfer_cancelled || (!useUsbHost() && !shared_thread_data->fp)) + { + if (useUsbHost() && shared_thread_data->transfer_cancelled) usbCancelFileTransfer(); + mutexUnlock(&g_fileMutex); + break; + } + + /* Write current file data chunk */ + if (useUsbHost()) + { + shared_thread_data->write_error = !usbSendFileData(shared_thread_data->data, shared_thread_data->data_size); + } else { + shared_thread_data->write_error = (fwrite(shared_thread_data->data, 1, shared_thread_data->data_size, shared_thread_data->fp) != shared_thread_data->data_size); + } + + if (!shared_thread_data->write_error) + { + shared_thread_data->data_written += shared_thread_data->data_size; + shared_thread_data->data_size = 0; + } + + /* Wake up the read thread to continue reading data */ + mutexUnlock(&g_fileMutex); + condvarWakeAll(&g_readCondvar); + + if (shared_thread_data->write_error) break; + } + +end: + threadExit(); +} + +static bool spanDumpThreads(ThreadFunc read_func, ThreadFunc write_func, void *arg) +{ + SharedThreadData *shared_thread_data = (SharedThreadData*)arg; // UB but we don't care + Thread read_thread = {0}, write_thread = {0}; + + time_t start = 0, btn_cancel_start_tmr = 0, btn_cancel_end_tmr = 0; + bool btn_cancel_cur_state = false, btn_cancel_prev_state = false, success = false; + + u64 prev_size = 0; + u8 prev_time = 0, percent = 0; + + consolePrint("creating threads\n"); + utilsCreateThread(&read_thread, read_func, arg, 2); + utilsCreateThread(&write_thread, write_func, arg, 2); + + consolePrint("hold b to cancel\n\n"); + consoleRefresh(); + + start = time(NULL); + + while(shared_thread_data->data_written < shared_thread_data->total_size) + { + g_appletStatus = appletMainLoop(); + if (!g_appletStatus) + { + mutexLock(&g_fileMutex); + shared_thread_data->transfer_cancelled = true; + mutexUnlock(&g_fileMutex); + } + + if (shared_thread_data->read_error || shared_thread_data->write_error || shared_thread_data->transfer_cancelled) break; + + struct tm ts = {0}; + time_t now = time(NULL); + localtime_r(&now, &ts); + + size_t size = shared_thread_data->data_written; + + utilsScanPads(); + btn_cancel_cur_state = (utilsGetButtonsHeld() & HidNpadButton_B); + + if (btn_cancel_cur_state && btn_cancel_cur_state != btn_cancel_prev_state) + { + btn_cancel_start_tmr = now; + } else + if (btn_cancel_cur_state && btn_cancel_cur_state == btn_cancel_prev_state) + { + btn_cancel_end_tmr = now; + if ((btn_cancel_end_tmr - btn_cancel_start_tmr) >= 3) + { + mutexLock(&g_fileMutex); + shared_thread_data->transfer_cancelled = true; + mutexUnlock(&g_fileMutex); + break; + } + } else { + btn_cancel_start_tmr = btn_cancel_end_tmr = 0; + } + + btn_cancel_prev_state = btn_cancel_cur_state; + + if (prev_time == ts.tm_sec || prev_size == size) continue; + + percent = (u8)((size * 100) / shared_thread_data->total_size); + + prev_time = ts.tm_sec; + prev_size = size; + + consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_thread_data->total_size, percent, (now - start)); + consoleRefresh(); + } + + consolePrint("\nwaiting for threads to join\n"); + consoleRefresh(); + + utilsJoinThread(&read_thread); + consolePrint("read_thread done: %lu\n", time(NULL)); + + utilsJoinThread(&write_thread); + consolePrint("write_thread done: %lu\n", time(NULL)); + + if (shared_thread_data->read_error || shared_thread_data->write_error) + { + consolePrint("i/o error\n"); + } else + if (shared_thread_data->transfer_cancelled) + { + consolePrint("process cancelled\n"); + } else { + start = (time(NULL) - start); + consolePrint("process completed in %lu seconds\n", start); + success = true; + } + + consoleRefresh(); + + return success; +} + +static void nspThreadFunc(void *arg) +{ + NspThreadData *nsp_thread_data = (NspThreadData*)arg; + + TitleInfo *title_info = NULL; + + bool set_download_type = (bool)getNspSetDownloadDistributionOption(); + bool remove_console_data = (bool)getNspRemoveConsoleDataOption(); + bool remove_titlekey_crypto = (bool)getNspRemoveTitlekeyCryptoOption(); + bool patch_sua = (bool)getNspDisableLinkedAccountRequirementOption(); + bool patch_screenshot = (bool)getNspEnableScreenshotsOption(); + bool patch_video_capture = (bool)getNspEnableVideoCaptureOption(); + bool patch_hdcp = (bool)getNspDisableHdcpOption(); + bool append_authoringtool_data = (bool)getNspAppendAuthoringToolDataOption(); + bool success = false; + + u64 free_space = 0; + u32 dev_idx = g_storageMenuElementOption.selected; + + u8 *buf = NULL; + char *filename = NULL; + FILE *fd = NULL; + + NcaContext *nca_ctx = NULL; + + NcaContext *meta_nca_ctx = NULL; + ContentMetaContext cnmt_ctx = {0}; + + ProgramInfoContext *program_info_ctx = NULL; + u32 program_idx = 0, program_count = 0; + + NacpContext *nacp_ctx = NULL; + u32 control_idx = 0, control_count = 0; + + LegalInfoContext *legal_info_ctx = NULL; + u32 legal_info_idx = 0, legal_info_count = 0; + + Ticket tik = {0}; + TikCommonBlock *tik_common_block = NULL; + + u8 *raw_cert_chain = NULL; + u64 raw_cert_chain_size = 0; + + PartitionFileSystemFileContext pfs_file_ctx = {0}; + pfsInitializeFileContext(&pfs_file_ctx); + + char entry_name[64] = {0}; + u64 nsp_header_size = 0, nsp_size = 0, nsp_offset = 0; + char *tmp_name = NULL; + + Sha256Context sha256_ctx = {0}; + u8 sha256_hash[SHA256_HASH_SIZE] = {0}; + + if (!nsp_thread_data || !(title_info = (TitleInfo*)nsp_thread_data->data) || !title_info->content_count || !title_info->content_infos) goto end; + + /* Allocate memory for the dump process. */ + if (!(buf = usbAllocatePageAlignedBuffer(BLOCK_SIZE))) + { + consolePrint("buf alloc failed\n"); + goto end; + } + + /* Generate output path. */ + filename = generateOutputTitleFileName(title_info, "NSP", ".nsp"); + if (!filename) goto end; + + /* Get free space on output storage. */ + if (dev_idx != 1 && !utilsGetFileSystemStatsByPath(filename, NULL, &free_space)) + { + consolePrint("failed to retrieve free space from selected device\n"); + goto end; + } + + if (!(nca_ctx = calloc(title_info->content_count, sizeof(NcaContext)))) + { + consolePrint("nca ctx calloc failed\n"); + goto end; + } + + // determine if we should initialize programinfo ctx + if (append_authoringtool_data) + { + program_count = titleGetContentCountByType(title_info, NcmContentType_Program); + if (program_count && !(program_info_ctx = calloc(program_count, sizeof(ProgramInfoContext)))) + { + consolePrint("program info ctx calloc failed\n"); + goto end; + } + } + + // determine if we should initialize nacp ctx + if (patch_sua || patch_screenshot || patch_video_capture || patch_hdcp || append_authoringtool_data) + { + control_count = titleGetContentCountByType(title_info, NcmContentType_Control); + if (control_count && !(nacp_ctx = calloc(control_count, sizeof(NacpContext)))) + { + consolePrint("nacp ctx calloc failed\n"); + goto end; + } + } + + // determine if we should initialize legalinfo ctx + if (append_authoringtool_data) + { + legal_info_count = titleGetContentCountByType(title_info, NcmContentType_LegalInformation); + if (legal_info_count && !(legal_info_ctx = calloc(legal_info_count, sizeof(LegalInfoContext)))) + { + consolePrint("legal info ctx calloc failed\n"); + goto end; + } + } + + // set meta nca as the last nca + meta_nca_ctx = &(nca_ctx[title_info->content_count - 1]); + + if (!ncaInitializeContext(meta_nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ + titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Meta, 0), title_info->version.value, &tik)) + { + consolePrint("Meta nca initialize ctx failed\n"); + goto end; + } + + consolePrint("Meta nca initialize ctx succeeded\n"); + + if (!cnmtInitializeContext(&cnmt_ctx, meta_nca_ctx)) + { + consolePrint("cnmt initialize ctx failed\n"); + goto end; + } + + consolePrint("cnmt initialize ctx succeeded (%s)\n", meta_nca_ctx->content_id_str); + + // initialize nca context + // initialize content type context + // generate nca patches (if needed) + // generate content type xml + for(u32 i = 0, j = 0; i < title_info->content_count; i++) + { + // skip meta nca since we already initialized it + NcmContentInfo *content_info = &(title_info->content_infos[i]); + if (content_info->content_type == NcmContentType_Meta) continue; + + NcaContext *cur_nca_ctx = &(nca_ctx[j]); + if (!ncaInitializeContext(cur_nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), content_info, title_info->version.value, &tik)) + { + consolePrint("%s #%u initialize nca ctx failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset); + goto end; + } + + consolePrint("%s #%u initialize nca ctx succeeded\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset); + + // don't go any further with this nca if we can't access its fs data because it's pointless + // TODO: add preload warning + if (cur_nca_ctx->rights_id_available && !cur_nca_ctx->titlekey_retrieved) + { + j++; + continue; + } + + // set download distribution type + // has no effect if this nca uses NcaDistributionType_Download + if (set_download_type) ncaSetDownloadDistributionType(cur_nca_ctx); + + // remove titlekey crypto + // has no effect if this nca doesn't use titlekey crypto + if (remove_titlekey_crypto && !ncaRemoveTitleKeyCrypto(cur_nca_ctx)) + { + consolePrint("nca remove titlekey crypto failed\n"); + goto end; + } + + if (!cur_nca_ctx->fs_ctx[0].has_sparse_layer) + { + switch(content_info->content_type) + { + case NcmContentType_Program: + { + // don't proceed if we didn't allocate programinfo ctx or if we're dealing with a sparse layer + if (!program_count || !program_info_ctx) break; + + ProgramInfoContext *cur_program_info_ctx = &(program_info_ctx[program_idx]); + + if (!programInfoInitializeContext(cur_program_info_ctx, cur_nca_ctx)) + { + consolePrint("initialize program info ctx failed (%s)\n", cur_nca_ctx->content_id_str); + goto end; + } + + if (!programInfoGenerateAuthoringToolXml(cur_program_info_ctx)) + { + consolePrint("program info xml failed (%s)\n", cur_nca_ctx->content_id_str); + goto end; + } + + program_idx++; + + consolePrint("initialize program info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str); + + break; + } + case NcmContentType_Control: + { + // don't proceed if we didn't allocate nacp ctx + if (!control_count || !nacp_ctx) break; + + NacpContext *cur_nacp_ctx = &(nacp_ctx[control_idx]); + + if (!nacpInitializeContext(cur_nacp_ctx, cur_nca_ctx)) + { + consolePrint("initialize nacp ctx failed (%s)\n", cur_nca_ctx->content_id_str); + goto end; + } + + if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture, patch_hdcp)) + { + consolePrint("nacp nca patch failed (%s)\n", cur_nca_ctx->content_id_str); + goto end; + } + + if (append_authoringtool_data && !nacpGenerateAuthoringToolXml(cur_nacp_ctx, title_info->version.value, cnmtGetRequiredTitleVersion(&cnmt_ctx))) + { + consolePrint("nacp xml failed (%s)\n", cur_nca_ctx->content_id_str); + goto end; + } + + control_idx++; + + consolePrint("initialize nacp ctx succeeded (%s)\n", cur_nca_ctx->content_id_str); + + break; + } + case NcmContentType_LegalInformation: + { + // don't proceed if we didn't allocate legalinfo ctx + if (!legal_info_count || !legal_info_ctx) break; + + LegalInfoContext *cur_legal_info_ctx = &(legal_info_ctx[legal_info_idx]); + + if (!legalInfoInitializeContext(cur_legal_info_ctx, cur_nca_ctx)) + { + consolePrint("initialize legal info ctx failed (%s)\n", cur_nca_ctx->content_id_str); + goto end; + } + + legal_info_idx++; + + consolePrint("initialize legal info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str); + + break; + } + default: + break; + } + } + + if (!ncaEncryptHeader(cur_nca_ctx)) + { + consolePrint("%s #%u encrypt nca header failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset); + goto end; + } + + j++; + } + + consoleRefresh(); + + // generate cnmt xml right away even though we don't yet have all the data we need + // This is because we need its size to calculate the full nsp size + if (append_authoringtool_data && !cnmtGenerateAuthoringToolXml(&cnmt_ctx, nca_ctx, title_info->content_count)) + { + consolePrint("cnmt xml #1 failed\n"); + goto end; + } + + bool retrieve_tik_cert = (!remove_titlekey_crypto && tik.size > 0); + if (retrieve_tik_cert) + { + if (!(tik_common_block = tikGetCommonBlock(tik.data))) + { + consolePrint("tik common block failed"); + goto end; + } + + if (remove_console_data && tik_common_block->titlekey_type == TikTitleKeyType_Personalized) + { + if (!tikConvertPersonalizedTicketToCommonTicket(&tik, &raw_cert_chain, &raw_cert_chain_size)) + { + consolePrint("tik convert failed\n"); + goto end; + } + } else { + raw_cert_chain = (title_info->storage_id == NcmStorageId_GameCard ? certRetrieveRawCertificateChainFromGameCardByRightsId(&(tik_common_block->rights_id), &raw_cert_chain_size) : \ + certGenerateRawCertificateChainBySignatureIssuer(tik_common_block->issuer, &raw_cert_chain_size)); + if (!raw_cert_chain) + { + consolePrint("cert failed\n"); + goto end; + } + } + } + + // add nca info + for(u32 i = 0; i < title_info->content_count; i++) + { + NcaContext *cur_nca_ctx = &(nca_ctx[i]); + sprintf(entry_name, "%s.%s", cur_nca_ctx->content_id_str, cur_nca_ctx->content_type == NcmContentType_Meta ? "cnmt.nca" : "nca"); + + if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_nca_ctx->content_size, NULL)) + { + consolePrint("pfs add entry failed: %s\n", entry_name); + goto end; + } + } + + // add cnmt xml info + if (append_authoringtool_data) + { + sprintf(entry_name, "%s.cnmt.xml", meta_nca_ctx->content_id_str); + if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cnmt_ctx.authoring_tool_xml_size, &(meta_nca_ctx->content_type_ctx_data_idx))) + { + consolePrint("pfs add entry failed: %s\n", entry_name); + goto end; + } + } + + // add content type ctx data info + u32 limit = append_authoringtool_data ? (title_info->content_count - 1) : 0; + for(u32 i = 0; i < limit; i++) + { + bool ret = false; + NcaContext *cur_nca_ctx = &(nca_ctx[i]); + if (!cur_nca_ctx->content_type_ctx) continue; + + switch(cur_nca_ctx->content_type) + { + case NcmContentType_Program: + { + ProgramInfoContext *cur_program_info_ctx = (ProgramInfoContext*)cur_nca_ctx->content_type_ctx; + sprintf(entry_name, "%s.programinfo.xml", cur_nca_ctx->content_id_str); + ret = pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_program_info_ctx->authoring_tool_xml_size, &(cur_nca_ctx->content_type_ctx_data_idx)); + break; + } + case NcmContentType_Control: + { + NacpContext *cur_nacp_ctx = (NacpContext*)cur_nca_ctx->content_type_ctx; + + for(u8 j = 0; j < cur_nacp_ctx->icon_count; j++) + { + NacpIconContext *icon_ctx = &(cur_nacp_ctx->icon_ctx[j]); + sprintf(entry_name, "%s.nx.%s.jpg", cur_nca_ctx->content_id_str, nacpGetLanguageString(icon_ctx->language)); + if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, icon_ctx->icon_size, j == 0 ? &(cur_nca_ctx->content_type_ctx_data_idx) : NULL)) + { + consolePrint("pfs add entry failed: %s\n", entry_name); + goto end; + } + } + + sprintf(entry_name, "%s.nacp.xml", cur_nca_ctx->content_id_str); + ret = pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_nacp_ctx->authoring_tool_xml_size, !cur_nacp_ctx->icon_count ? &(cur_nca_ctx->content_type_ctx_data_idx) : NULL); + break; + } + case NcmContentType_LegalInformation: + { + LegalInfoContext *cur_legal_info_ctx = (LegalInfoContext*)cur_nca_ctx->content_type_ctx; + sprintf(entry_name, "%s.legalinfo.xml", cur_nca_ctx->content_id_str); + ret = pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_legal_info_ctx->authoring_tool_xml_size, &(cur_nca_ctx->content_type_ctx_data_idx)); + break; + } + default: + break; + } + + if (!ret) + { + consolePrint("pfs add entry failed: %s\n", entry_name); + goto end; + } + } + + // add ticket and cert info + if (retrieve_tik_cert) + { + sprintf(entry_name, "%s.tik", tik.rights_id_str); + if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, tik.size, NULL)) + { + consolePrint("pfs add entry failed: %s\n", entry_name); + goto end; + } + + sprintf(entry_name, "%s.cert", tik.rights_id_str); + if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, raw_cert_chain_size, NULL)) + { + consolePrint("pfs add entry failed: %s\n", entry_name); + goto end; + } + } + + // write buffer to memory buffer + if (!pfsWriteFileContextHeaderToMemoryBuffer(&pfs_file_ctx, buf, BLOCK_SIZE, &nsp_header_size)) + { + consolePrint("pfs write header to mem #1 failed\n"); + goto end; + } + + nsp_size = (nsp_header_size + pfs_file_ctx.fs_size); + consolePrint("nsp header size: 0x%lX | nsp size: 0x%lX\n", nsp_header_size, nsp_size); + consoleRefresh(); + + if (dev_idx == 1) + { + if (!usbSendNspProperties(nsp_size, filename, (u32)nsp_header_size)) + { + consolePrint("usb send nsp properties failed\n"); + goto end; + } + } else { + if (nsp_size >= free_space) + { + consolePrint("nsp size exceeds free space\n"); + goto end; + } + + utilsCreateDirectoryTree(filename, false); + + if (dev_idx == 0) + { + if (nsp_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 && nsp_size > FAT32_FILESIZE_LIMIT) + { + consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n"); + goto end; + } + } + + if (!(fd = fopen(filename, "wb"))) + { + consolePrint("fopen failed\n"); + goto end; + } + + // set file size + ftruncate(fileno(fd), (off_t)nsp_size); + + // write placeholder header + memset(buf, 0, nsp_header_size); + fwrite(buf, 1, nsp_header_size, fd); + } + + consolePrint("dump process started, please wait. hold b to cancel.\n"); + consoleRefresh(); + + nsp_offset += nsp_header_size; + + // set nsp size + nsp_thread_data->total_size = nsp_size; + + // write ncas + for(u32 i = 0; i < title_info->content_count; i++) + { + NcaContext *cur_nca_ctx = &(nca_ctx[i]); + u64 blksize = BLOCK_SIZE; + + memset(&sha256_ctx, 0, sizeof(Sha256Context)); + sha256ContextCreate(&sha256_ctx); + + if (cur_nca_ctx->content_type == NcmContentType_Meta && (!cnmtGenerateNcaPatch(&cnmt_ctx) || !ncaEncryptHeader(cur_nca_ctx))) + { + consolePrint("cnmt generate patch failed\n"); + goto end; + } + + bool dirty_header = ncaIsHeaderDirty(cur_nca_ctx); + + if (dev_idx == 1) + { + tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, i); + if (!usbSendFileProperties(cur_nca_ctx->content_size, tmp_name)) + { + consolePrint("usb send file properties \"%s\" failed\n", tmp_name); + goto end; + } + } + + for(u64 offset = 0; offset < cur_nca_ctx->content_size; offset += blksize, nsp_offset += blksize, nsp_thread_data->data_written += blksize) + { + mutexLock(&g_fileMutex); + bool cancelled = nsp_thread_data->transfer_cancelled; + mutexUnlock(&g_fileMutex); + + if (cancelled) + { + if (dev_idx == 1) usbCancelFileTransfer(); + goto end; + } + + if ((cur_nca_ctx->content_size - offset) < blksize) blksize = (cur_nca_ctx->content_size - offset); + + // read nca chunk + if (!ncaReadContentFile(cur_nca_ctx, buf, blksize, offset)) + { + consolePrint("nca read failed at 0x%lX for \"%s\"\n", offset, cur_nca_ctx->content_id_str); + goto end; + } + + if (dirty_header) + { + // write re-encrypted headers + if (!cur_nca_ctx->header_written) ncaWriteEncryptedHeaderDataToMemoryBuffer(cur_nca_ctx, buf, blksize, offset); + + if (cur_nca_ctx->content_type_ctx_patch) + { + // write content type context patch + switch(cur_nca_ctx->content_type) + { + case NcmContentType_Meta: + cnmtWriteNcaPatch(&cnmt_ctx, buf, blksize, offset); + break; + case NcmContentType_Control: + nacpWriteNcaPatch((NacpContext*)cur_nca_ctx->content_type_ctx, buf, blksize, offset); + break; + default: + break; + } + } + + // update flag to avoid entering this code block if it's not needed anymore + dirty_header = (!cur_nca_ctx->header_written || cur_nca_ctx->content_type_ctx_patch); + } + + // update hash calculation + sha256ContextUpdate(&sha256_ctx, buf, blksize); + + // write nca chunk + if (dev_idx == 1) + { + if (!usbSendFileData(buf, blksize)) + { + consolePrint("send file data failed\n"); + goto end; + } + } else { + fwrite(buf, 1, blksize, fd); + } + } + + // get hash + sha256ContextGetHash(&sha256_ctx, sha256_hash); + + // update content id and hash + ncaUpdateContentIdAndHash(cur_nca_ctx, sha256_hash); + + // update cnmt + if (!cnmtUpdateContentInfo(&cnmt_ctx, cur_nca_ctx)) + { + consolePrint("cnmt update content info failed\n"); + goto end; + } + + // update pfs entry name + if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, i, cur_nca_ctx->content_id_str)) + { + consolePrint("pfs update entry name failed for nca \"%s\"\n", cur_nca_ctx->content_id_str); + goto end; + } + } + + if (append_authoringtool_data) + { + // regenerate cnmt xml + if (!cnmtGenerateAuthoringToolXml(&cnmt_ctx, nca_ctx, title_info->content_count)) + { + consolePrint("cnmt xml #2 failed\n"); + goto end; + } + + // write cnmt xml + if (dev_idx == 1) + { + tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, meta_nca_ctx->content_type_ctx_data_idx); + if (!usbSendFileProperties(cnmt_ctx.authoring_tool_xml_size, tmp_name) || !usbSendFileData(cnmt_ctx.authoring_tool_xml, cnmt_ctx.authoring_tool_xml_size)) + { + consolePrint("send \"%s\" failed\n", tmp_name); + goto end; + } + } else { + fwrite(cnmt_ctx.authoring_tool_xml, 1, cnmt_ctx.authoring_tool_xml_size, fd); + } + + nsp_offset += cnmt_ctx.authoring_tool_xml_size; + nsp_thread_data->data_written += cnmt_ctx.authoring_tool_xml_size; + + // update cnmt xml pfs entry name + if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, meta_nca_ctx->content_type_ctx_data_idx, meta_nca_ctx->content_id_str)) + { + consolePrint("pfs update entry name cnmt xml failed\n"); + goto end; + } + } + + // write content type ctx data + for(u32 i = 0; i < limit; i++) + { + NcaContext *cur_nca_ctx = &(nca_ctx[i]); + if (!cur_nca_ctx->content_type_ctx) continue; + + char *authoring_tool_xml = NULL; + u64 authoring_tool_xml_size = 0; + u32 data_idx = cur_nca_ctx->content_type_ctx_data_idx; + + switch(cur_nca_ctx->content_type) + { + case NcmContentType_Program: + { + ProgramInfoContext *cur_program_info_ctx = (ProgramInfoContext*)cur_nca_ctx->content_type_ctx; + authoring_tool_xml = cur_program_info_ctx->authoring_tool_xml; + authoring_tool_xml_size = cur_program_info_ctx->authoring_tool_xml_size; + break; + } + case NcmContentType_Control: + { + NacpContext *cur_nacp_ctx = (NacpContext*)cur_nca_ctx->content_type_ctx; + authoring_tool_xml = cur_nacp_ctx->authoring_tool_xml; + authoring_tool_xml_size = cur_nacp_ctx->authoring_tool_xml_size; + + // loop through available icons + for(u8 j = 0; j < cur_nacp_ctx->icon_count; j++) + { + NacpIconContext *icon_ctx = &(cur_nacp_ctx->icon_ctx[j]); + + // write icon + if (dev_idx == 1) + { + tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, data_idx); + if (!usbSendFileProperties(icon_ctx->icon_size, tmp_name) || !usbSendFileData(icon_ctx->icon_data, icon_ctx->icon_size)) + { + consolePrint("send \"%s\" failed\n", tmp_name); + goto end; + } + } else { + fwrite(icon_ctx->icon_data, 1, icon_ctx->icon_size, fd); + } + + nsp_offset += icon_ctx->icon_size; + nsp_thread_data->data_written += icon_ctx->icon_size; + + // update pfs entry name + if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, data_idx++, cur_nca_ctx->content_id_str)) + { + consolePrint("pfs update entry name failed for icon \"%s\" (%u)\n", cur_nca_ctx->content_id_str, icon_ctx->language); + goto end; + } + } + + break; + } + case NcmContentType_LegalInformation: + { + LegalInfoContext *cur_legal_info_ctx = (LegalInfoContext*)cur_nca_ctx->content_type_ctx; + authoring_tool_xml = cur_legal_info_ctx->authoring_tool_xml; + authoring_tool_xml_size = cur_legal_info_ctx->authoring_tool_xml_size; + break; + } + default: + break; + } + + // write xml + if (dev_idx == 1) + { + tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, data_idx); + if (!usbSendFileProperties(authoring_tool_xml_size, tmp_name) || !usbSendFileData(authoring_tool_xml, authoring_tool_xml_size)) + { + consolePrint("send \"%s\" failed\n", tmp_name); + goto end; + } + } else { + fwrite(authoring_tool_xml, 1, authoring_tool_xml_size, fd); + } + + nsp_offset += authoring_tool_xml_size; + nsp_thread_data->data_written += authoring_tool_xml_size; + + // update pfs entry name + if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, data_idx, cur_nca_ctx->content_id_str)) + { + consolePrint("pfs update entry name failed for xml \"%s\"\n", cur_nca_ctx->content_id_str); + goto end; + } + } + + if (retrieve_tik_cert) + { + // write ticket + if (dev_idx == 1) + { + tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, pfs_file_ctx.header.entry_count - 2); + if (!usbSendFileProperties(tik.size, tmp_name) || !usbSendFileData(tik.data, tik.size)) + { + consolePrint("send \"%s\" failed\n", tmp_name); + goto end; + } + } else { + fwrite(tik.data, 1, tik.size, fd); + } + + nsp_offset += tik.size; + nsp_thread_data->data_written += tik.size; + + // write cert + if (dev_idx == 1) + { + tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, pfs_file_ctx.header.entry_count - 1); + if (!usbSendFileProperties(raw_cert_chain_size, tmp_name) || !usbSendFileData(raw_cert_chain, raw_cert_chain_size)) + { + consolePrint("send \"%s\" failed\n", tmp_name); + goto end; + } + } else { + fwrite(raw_cert_chain, 1, raw_cert_chain_size, fd); + } + + nsp_offset += raw_cert_chain_size; + nsp_thread_data->data_written += raw_cert_chain_size; + } + + // write new pfs0 header + if (!pfsWriteFileContextHeaderToMemoryBuffer(&pfs_file_ctx, buf, BLOCK_SIZE, &nsp_header_size)) + { + consolePrint("pfs write header to mem #2 failed\n"); + goto end; + } + + if (dev_idx == 1) + { + if (!usbSendNspHeader(buf, (u32)nsp_header_size)) + { + consolePrint("send nsp header failed\n"); + goto end; + } + } else { + rewind(fd); + fwrite(buf, 1, nsp_header_size, fd); + } + + nsp_thread_data->data_written += nsp_header_size; + + success = true; + +end: + consoleRefresh(); + + mutexLock(&g_fileMutex); + if (!success && !nsp_thread_data->transfer_cancelled) nsp_thread_data->error = true; + mutexUnlock(&g_fileMutex); + + if (fd) + { + fclose(fd); + + if (!success && dev_idx != 1) + { + if (dev_idx == 0) + { + utilsRemoveConcatenationFile(filename); + utilsCommitSdCardFileSystemChanges(); + } else { + remove(filename); + } + } + } + + pfsFreeFileContext(&pfs_file_ctx); + + if (raw_cert_chain) free(raw_cert_chain); + + if (legal_info_ctx) + { + for(u32 i = 0; i < legal_info_count; i++) legalInfoFreeContext(&(legal_info_ctx[i])); + free(legal_info_ctx); + } + + if (nacp_ctx) + { + for(u32 i = 0; i < control_count; i++) nacpFreeContext(&(nacp_ctx[i])); + free(nacp_ctx); + } + + if (program_info_ctx) + { + for(u32 i = 0; i < program_count; i++) programInfoFreeContext(&(program_info_ctx[i])); + free(program_info_ctx); + } + + cnmtFreeContext(&cnmt_ctx); + + if (nca_ctx) free(nca_ctx); + + if (filename) free(filename); + + if (buf) free(buf); + + threadExit(); +} + +static u32 getOutputStorageOption(void) +{ + return (u32)configGetInteger("output_storage"); +} + +static void setOutputStorageOption(u32 idx) +{ + if (idx < ConfigOutputStorage_Count) configSetInteger("output_storage", (int)idx); +} + +static u32 getGameCardPrependKeyAreaOption(void) +{ + return (u32)configGetBoolean("gamecard/prepend_key_area"); +} + +static void setGameCardPrependKeyAreaOption(u32 idx) +{ + configSetBoolean("gamecard/prepend_key_area", (bool)idx); +} + +static u32 getGameCardKeepCertificateOption(void) +{ + return (u32)configGetBoolean("gamecard/keep_certificate"); +} + +static void setGameCardKeepCertificateOption(u32 idx) +{ + configSetBoolean("gamecard/keep_certificate", (bool)idx); +} + +static u32 getGameCardTrimDumpOption(void) +{ + return (u32)configGetBoolean("gamecard/trim_dump"); +} + +static void setGameCardTrimDumpOption(u32 idx) +{ + configSetBoolean("gamecard/trim_dump", (bool)idx); +} + +static u32 getGameCardCalculateChecksumOption(void) +{ + return (u32)configGetBoolean("gamecard/calculate_checksum"); +} + +static void setGameCardCalculateChecksumOption(u32 idx) +{ + configSetBoolean("gamecard/calculate_checksum", (bool)idx); +} + +static u32 getGameCardWriteRawHfsPartitionOption(void) +{ + return (u32)configGetBoolean("gamecard/write_raw_hfs_partition"); +} + +static void setGameCardWriteRawHfsPartitionOption(u32 idx) +{ + configSetBoolean("gamecard/write_raw_hfs_partition", (bool)idx); +} + +static u32 getNspSetDownloadDistributionOption(void) +{ + return (u32)configGetBoolean("nsp/set_download_distribution"); +} + +static void setNspSetDownloadDistributionOption(u32 idx) +{ + configSetBoolean("nsp/set_download_distribution", (bool)idx); +} + +static u32 getNspRemoveConsoleDataOption(void) +{ + return (u32)configGetBoolean("nsp/remove_console_data"); +} + +static void setNspRemoveConsoleDataOption(u32 idx) +{ + configSetBoolean("nsp/remove_console_data", (bool)idx); +} + +static u32 getNspRemoveTitlekeyCryptoOption(void) +{ + return (u32)configGetBoolean("nsp/remove_titlekey_crypto"); +} + +static void setNspRemoveTitlekeyCryptoOption(u32 idx) +{ + configSetBoolean("nsp/remove_titlekey_crypto", (bool)idx); +} + +static u32 getNspDisableLinkedAccountRequirementOption(void) +{ + return (u32)configGetBoolean("nsp/disable_linked_account_requirement"); +} + +static void setNspDisableLinkedAccountRequirementOption(u32 idx) +{ + configSetBoolean("nsp/disable_linked_account_requirement", (bool)idx); +} + +static u32 getNspEnableScreenshotsOption(void) +{ + return (u32)configGetBoolean("nsp/enable_screenshots"); +} + +static void setNspEnableScreenshotsOption(u32 idx) +{ + configSetBoolean("nsp/enable_screenshots", (bool)idx); +} + +static u32 getNspEnableVideoCaptureOption(void) +{ + return (u32)configGetBoolean("nsp/enable_video_capture"); +} + +static void setNspEnableVideoCaptureOption(u32 idx) +{ + configSetBoolean("nsp/enable_video_capture", (bool)idx); +} + +static u32 getNspDisableHdcpOption(void) +{ + return (u32)configGetBoolean("nsp/disable_hdcp"); +} + +static void setNspDisableHdcpOption(u32 idx) +{ + configSetBoolean("nsp/disable_hdcp", (bool)idx); +} + +static u32 getNspAppendAuthoringToolDataOption(void) +{ + return (u32)configGetBoolean("nsp/append_authoringtool_data"); +} + +static void setNspAppendAuthoringToolDataOption(u32 idx) +{ + configSetBoolean("nsp/append_authoringtool_data", (bool)idx); +} + +static u32 getTicketRemoveConsoleDataOption(void) +{ + return (u32)configGetBoolean("ticket/remove_console_data"); +} + +static void setTicketRemoveConsoleDataOption(u32 idx) +{ + configSetBoolean("ticket/remove_console_data", (bool)idx); +} diff --git a/code_templates/sd_romfs_dumper.c b/code_templates/sd_romfs_dumper.c index 06ff1dd..5dbf2ff 100644 --- a/code_templates/sd_romfs_dumper.c +++ b/code_templates/sd_romfs_dumper.c @@ -158,6 +158,9 @@ static void read_thread_func(void *arg) break; } + /* Set file size. */ + ftruncate(fileno(shared_data->fd), (off_t)file_entry->size); + for(u64 offset = 0, blksize = BLOCK_SIZE; offset < file_entry->size; offset += blksize) { if (blksize > (file_entry->size - offset)) blksize = (file_entry->size - offset); @@ -585,7 +588,7 @@ int main(int argc, char *argv[]) consoleClear(); consolePrint("selected title:\n%s (%016lX)\n\n", app_metadata[selected_idx]->lang_entry.name, app_metadata[selected_idx]->title_id + program_id_offset); - if (!ncaInitializeContext(base_nca_ctx, user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \ + if (!ncaInitializeContext(base_nca_ctx, user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Program, program_id_offset), user_app_data.app_info->version.value, NULL)) { consolePrint("nca initialize base ctx failed\n"); @@ -611,7 +614,7 @@ int main(int argc, char *argv[]) { consolePrint("using patch romfs with update v%u\n", latest_patch->version.value); - if (!ncaInitializeContext(update_nca_ctx, latest_patch->storage_id, (latest_patch->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \ + if (!ncaInitializeContext(update_nca_ctx, latest_patch->storage_id, (latest_patch->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ titleGetContentInfoByTypeAndIdOffset(latest_patch, NcmContentType_Program, program_id_offset), latest_patch->version.value, NULL)) { consolePrint("nca initialize update ctx failed\n"); diff --git a/code_templates/system_title_dumper.c b/code_templates/system_title_dumper.c index a170697..3ab3bb9 100644 --- a/code_templates/system_title_dumper.c +++ b/code_templates/system_title_dumper.c @@ -122,6 +122,9 @@ static void dumpPartitionFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx) goto end; } + /* Set file size. */ + ftruncate(fileno(filefd), (off_t)pfs_entry->size); + consolePrint("dumping \"%s\"...\n", pfs_entry_name); u64 blksize = BLOCK_SIZE; @@ -195,6 +198,9 @@ static void dumpRomFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx) goto end; } + /* Set file size. */ + ftruncate(fileno(filefd), (off_t)romfs_file_entry->size); + consolePrint("dumping \"%s\"...\n", path + path_len); u64 blksize = BLOCK_SIZE; diff --git a/code_templates/usb_romfs_dumper.c b/code_templates/usb_romfs_dumper.c index 3b20835..024aa98 100644 --- a/code_templates/usb_romfs_dumper.c +++ b/code_templates/usb_romfs_dumper.c @@ -161,7 +161,7 @@ static void read_thread_func(void *arg) if (shared_data->write_error) break; /* Send current file properties */ - shared_data->read_error = !usbSendFilePropertiesCommon(file_entry->size, path); + shared_data->read_error = !usbSendFileProperties(file_entry->size, path); if (shared_data->read_error) { condvarWakeAll(&g_writeCondvar); @@ -579,7 +579,7 @@ int main(int argc, char *argv[]) consoleClear(); consolePrint("selected title:\n%s (%016lX)\n\n", app_metadata[selected_idx]->lang_entry.name, app_metadata[selected_idx]->title_id + program_id_offset); - if (!ncaInitializeContext(base_nca_ctx, user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \ + if (!ncaInitializeContext(base_nca_ctx, user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Program, program_id_offset), user_app_data.app_info->version.value, NULL)) { consolePrint("nca initialize base ctx failed\n"); @@ -605,7 +605,7 @@ int main(int argc, char *argv[]) { consolePrint("using patch romfs with update v%u\n", latest_patch->version.value); - if (!ncaInitializeContext(update_nca_ctx, latest_patch->storage_id, (latest_patch->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \ + if (!ncaInitializeContext(update_nca_ctx, latest_patch->storage_id, (latest_patch->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ titleGetContentInfoByTypeAndIdOffset(latest_patch, NcmContentType_Program, program_id_offset), latest_patch->version.value, NULL)) { consolePrint("nca initialize update ctx failed\n"); diff --git a/code_templates/xml_generator.c b/code_templates/xml_generator.c index f9682c4..1d92cad 100644 --- a/code_templates/xml_generator.c +++ b/code_templates/xml_generator.c @@ -280,7 +280,7 @@ int main(int argc, char *argv[]) NcmContentInfo *content_info = &(user_app_data.app_info->content_infos[i]); if (content_info->content_type == NcmContentType_Meta) continue; - if (!ncaInitializeContext(&(nca_ctx[j]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \ + if (!ncaInitializeContext(&(nca_ctx[j]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ content_info, user_app_data.app_info->version.value, &tik)) { consolePrint("%s #%u initialize nca ctx failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset); @@ -330,7 +330,7 @@ int main(int argc, char *argv[]) j++; } - if (!ncaInitializeContext(&(nca_ctx[meta_idx]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \ + if (!ncaInitializeContext(&(nca_ctx[meta_idx]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Meta, 0), user_app_data.app_info->version.value, &tik)) { consolePrint("Meta nca initialize ctx failed\n"); diff --git a/host/README.md b/host/README.md index e5abd31..488b02d 100644 --- a/host/README.md +++ b/host/README.md @@ -1,6 +1,6 @@ # nxdumptool USB Application Binary Interface (ABI) Technical Specification -This Markdown document aims to explain the technical details behind the ABI used by nxdumptool to communicate with a USB host device connected to the console. As of this writing (May 11th, 2021), the current ABI version is `1`. +This Markdown document aims to explain the technical details behind the ABI used by nxdumptool to communicate with a USB host device connected to the console. As of this writing (April 19th, 2023), the current ABI version is `1.1`. In order to avoid unnecessary clutter, this document assumes the reader is already familiar with homebrew launching on the Nintendo Switch, as well as USB concepts such as device/configuration/interface/endpoint descriptors and bulk mode transfers. Shall this not be the case, a small list of helpful resources is available at the end of this document. @@ -109,14 +109,14 @@ All commands, with the exception of `CancelFileTransfer` and `EndSession`, yield #### StartSession -| Offset | Size | Description | -|:------:|------|-------------------------------------------| -| 0x00 | 0x01 | nxdumptool version (major). | -| 0x01 | 0x01 | nxdumptool version (minor). | -| 0x02 | 0x01 | nxdumptool version (micro). | -| 0x03 | 0x01 | nxdumptool USB ABI version. | -| 0x04 | 0x08 | Git commit hash (NULL terminated string). | -| 0x0C | 0x04 | Reserved. | +| Offset | Size | Description | +|:------:|------|---------------------------------------------------------------------| +| 0x00 | 0x01 | nxdumptool version (major). | +| 0x01 | 0x01 | nxdumptool version (minor). | +| 0x02 | 0x01 | nxdumptool version (micro). | +| 0x03 | 0x01 | nxdumptool USB ABI version (high nibble: major, low nibble: minor). | +| 0x04 | 0x08 | Git commit hash (NULL terminated string). | +| 0x0C | 0x04 | Reserved. | This is the first USB command issued by nxdumptool upon connection to a USB host device. If it succeeds, further USB commands may be sent. @@ -180,7 +180,7 @@ Status responses are expected by nxdumptool at certain points throughout the com * Right after receiving a command header and/or command block. * Right after receiving the last file data chunk from a [SendFileProperties](#sendfileproperties) command. -The endpoint max packet size must be sent back to the target console using status responses because the `usb:ds` API provides no way for homebrew applications to know which device descriptor and/or speed was selected by the USB host device. +The endpoint max packet size must be sent back to the target console using status responses because `usb:ds` API's `GetUsbDeviceSpeed` cmd is only available under Horizon OS 8.0.0+ -- and we definitely want to provide USB communication support under lower versions. #### Status codes diff --git a/host/nxdt_host.py b/host/nxdt_host.py index 1906ca9..f44417f 100644 --- a/host/nxdt_host.py +++ b/host/nxdt_host.py @@ -45,6 +45,7 @@ import struct import usb.core import usb.util import warnings +import base64 import tkinter as tk import tkinter.ttk as ttk @@ -52,10 +53,11 @@ from tkinter import filedialog, messagebox, font, scrolledtext from tqdm import tqdm -import base64 - from argparse import ArgumentParser +from io import BufferedWriter +from typing import List, Tuple, Any, Callable, Optional + # Scaling factors. WINDOWS_SCALING_FACTOR = 96.0 SCALE = 1.0 @@ -65,7 +67,7 @@ WINDOW_WIDTH = 500 WINDOW_HEIGHT = 470 # Application version. -APP_VERSION = '0.3' +APP_VERSION = '0.4' # Copyright year. COPYRIGHT_YEAR = '2020-2023' @@ -91,7 +93,8 @@ USB_TRANSFER_THRESHOLD = (USB_TRANSFER_BLOCK_SIZE * 4) USB_MAGIC_WORD = b'NXDT' # Supported USB ABI version. -USB_ABI_VERSION = 1 +USB_ABI_VERSION_MAJOR = 1 +USB_ABI_VERSION_MINOR = 1 # USB command header size. USB_CMD_HEADER_SIZE = 0x10 @@ -119,14 +122,14 @@ USB_STATUS_MALFORMED_CMD = 7 USB_STATUS_HOST_IO_ERROR = 8 # Script title. -SCRIPT_TITLE = "{} host script v{}".format(USB_DEV_PRODUCT, APP_VERSION) +SCRIPT_TITLE = f'{USB_DEV_PRODUCT} host script v{APP_VERSION}' # Copyright text. -COPYRIGHT_TEXT = "Copyright (c) {}, {}".format(COPYRIGHT_YEAR, USB_DEV_MANUFACTURER) +COPYRIGHT_TEXT = f'Copyright (c) {COPYRIGHT_YEAR}, {USB_DEV_MANUFACTURER}' # Messages displayed as labels. -SERVER_START_MSG = 'Please connect a Nintendo Switch console running {}.'.format(USB_DEV_PRODUCT) -SERVER_STOP_MSG = 'Exit {} on your console or disconnect it at any time to stop the server.'.format(USB_DEV_PRODUCT) +SERVER_START_MSG = f'Please connect a Nintendo Switch console running {USB_DEV_PRODUCT}.' +SERVER_STOP_MSG = f'Exit {USB_DEV_PRODUCT} on your console or disconnect it at any time to stop the server.' # Default directory paths. INITIAL_DIR = os.path.abspath(os.path.dirname(sys.executable if getattr(sys, 'frozen', False) else __file__)) @@ -292,13 +295,57 @@ TASKBAR_LIB = b'TVNGVAIAAQAAAAAACQQAAAAAAABBAAAAAQAAAAAAAAAOAAAA/////wAAAAAAAAAA b'AAAAABQAAQADAAOAAAAAAAAAJAAEAAAAFAACAAMAA4AAAAAAAAAkAAgAAAAUAAMAAwADgAAAAAAAACQADAAAAAAAAEABAABAAgAAQAMAAEC0BgAAxAYAANQGAADoBgAA' + \ b'AAAAABQAAAAoAAAAPAAAAA==' +# Global variables used throughout the code. +g_cliMode: bool = False +g_outputDir: str = '' + +g_osType: str = '' +g_osVersion: str = '' + +g_isWindows: bool = False +g_isWindowsVista: bool = False +g_isWindows7: bool = False + +g_tkRoot: Optional[tk.Tk] = None +g_tkCanvas: Optional[tk.Canvas] = None +g_tkDirText: Optional[tk.Text] = None +g_tkChooseDirButton: Optional[tk.Button] = None +g_tkServerButton: Optional[tk.Button] = None +g_tkTipMessage: Any = None +g_tkScrolledTextLog: Optional[scrolledtext.ScrolledText] = None + +g_logger: Optional[logging.Logger] = None + +g_stopEvent: Optional[threading.Event] = None + +g_tlb: Any = None +g_taskbar: Any = None + +g_usbEpIn: Any = None +g_usbEpOut: Any = None +g_usbEpMaxPacketSize: int = 0 + +g_nxdtVersionMajor: int = 0 +g_nxdtVersionMinor: int = 0 +g_nxdtVersionMicro: int = 0 +g_nxdtAbiVersionMajor: int = 0 +g_nxdtAbiVersionMinor: int = 0 +g_nxdtGitCommit: str = '' + +g_nspTransferMode: bool = False +g_nspSize: int = 0 +g_nspHeaderSize: int = 0 +g_nspRemainingSize: int = 0 +g_nspFile: Optional[BufferedWriter] = None +g_nspFilePath: str = '' + # Reference: https://beenje.github.io/blog/posts/logging-to-a-tkinter-scrolledtext-widget. class LogQueueHandler(logging.Handler): - def __init__(self, log_queue): + def __init__(self, log_queue: queue.Queue): super().__init__() self.log_queue = log_queue - def emit(self, record): + def emit(self, record: logging.LogRecord) -> None: if g_cliMode: msg = self.format(record) print(msg) @@ -307,12 +354,14 @@ class LogQueueHandler(logging.Handler): # Reference: https://beenje.github.io/blog/posts/logging-to-a-tkinter-scrolledtext-widget. class LogConsole: - def __init__(self, scrolled_text=None): + def __init__(self, scrolled_text: Optional[scrolledtext.ScrolledText] = None): + #assert g_logger is not None + self.scrolled_text = scrolled_text self.frame = (self.scrolled_text.winfo_toplevel() if self.scrolled_text else None) # Create a logging handler using a queue. - self.log_queue = queue.Queue() + self.log_queue: queue.Queue = queue.Queue() self.queue_handler = LogQueueHandler(self.log_queue) #formatter = logging.Formatter('[%(asctime)s] -> %(message)s') formatter = logging.Formatter('%(message)s') @@ -320,9 +369,10 @@ class LogConsole: g_logger.addHandler(self.queue_handler) # Start polling messages from the queue. - if self.frame: self.frame.after(100, self.poll_log_queue) + if self.frame: + self.frame.after(100, self.poll_log_queue) - def display(self, record): + def display(self, record: logging.LogRecord) -> None: if self.scrolled_text: msg = self.queue_handler.format(record) self.scrolled_text.configure(state='normal') @@ -330,7 +380,7 @@ class LogConsole: self.scrolled_text.configure(state='disabled') self.scrolled_text.yview(tk.END) - def poll_log_queue(self): + def poll_log_queue(self) -> None: # Check every 100 ms if there is a new message in the queue to display. while True: try: @@ -340,40 +390,45 @@ class LogConsole: else: self.display(record) - if self.frame: self.frame.after(100, self.poll_log_queue) + if self.frame: + self.frame.after(100, self.poll_log_queue) # Loosely based on tk.py from tqdm. class ProgressBarWindow: global g_tlb, g_taskbar - def __init__(self, bar_format=None, tk_parent=None, window_title='', window_resize=False, window_protocol=None): - self.n = 0 - self.total = 0 - self.divider = 1 - self.total_div = 0 - self.prefix = '' - self.unit = 'B' + def __init__(self, bar_format: str = '', tk_parent: Any = None, window_title: str = '', window_resize: bool = False, window_protocol: Optional[Callable] = None): + self.n: int = 0 + self.total: int = 0 + self.divider: float = 1.0 + self.total_div: float = 0 + self.prefix: str = '' + self.unit: str = 'B' self.bar_format = bar_format - self.start_time = 0 - self.elapsed_time = 0 - self.hwnd = 0 + self.start_time: float = 0 + self.elapsed_time: float = 0 + self.hwnd: int = 0 self.tk_parent = tk_parent self.tk_window = (tk.Toplevel(self.tk_parent) if self.tk_parent else None) self.withdrawn = False - self.tk_text_var = None - self.tk_n_var = None - self.tk_pbar = None + self.tk_text_var: Optional[tk.StringVar] = None + self.tk_n_var: Optional[tk.DoubleVar] = None + self.tk_pbar: Optional[ttk.Progressbar] = None - self.pbar = None + self.pbar: Optional[tqdm] = None if self.tk_window: self.tk_window.withdraw() self.withdrawn = True - if window_title: self.tk_window.title(window_title) + if window_title: + self.tk_window.title(window_title) + self.tk_window.resizable(window_resize, window_resize) - if window_protocol: self.tk_window.protocol('WM_DELETE_WINDOW', window_protocol) + + if window_protocol: + self.tk_window.protocol('WM_DELETE_WINDOW', window_protocol) pbar_frame = ttk.Frame(self.tk_window, padding=5) pbar_frame.pack() @@ -388,10 +443,12 @@ class ProgressBarWindow: self.tk_pbar.pack() def __del__(self): - if self.tk_parent: self.tk_parent.after(0, self.tk_window.destroy) + if self.tk_parent: + self.tk_parent.after(0, self.tk_window.destroy) - def start(self, total, n=0, divider=1, prefix='', unit='B'): - if (total <= 0) or (n < 0) or (divider < 1): raise Exception('Invalid arguments!') + def start(self, total: int, n: int = 0, divider: int = 1, prefix: str = '', unit: str = 'B') -> None: + if (total <= 0) or (n < 0) or (divider < 1): + raise Exception('Invalid arguments!') self.n = n self.total = total @@ -407,11 +464,15 @@ class ProgressBarWindow: n_div = (float(self.n) / self.divider) self.pbar = tqdm(initial=n_div, total=self.total_div, unit=self.unit, dynamic_ncols=True, desc=self.prefix, bar_format=self.bar_format) - def update(self, n): + def update(self, n: int) -> None: cur_n = (self.n + n) - if cur_n > self.total: return + if cur_n > self.total: + return if self.tk_window: + #assert self.tk_text_var is not None + #assert self.tk_n_var is not None + cur_n_div = (float(cur_n) / self.divider) self.elapsed_time = (time.time() - self.start_time) @@ -421,7 +482,7 @@ class ProgressBarWindow: self.tk_n_var.set(cur_n_div) if self.withdrawn: - self.tk_window.geometry("+{}+{}".format(self.tk_parent.winfo_x(), self.tk_parent.winfo_y())) + self.tk_window.geometry(f'+{self.tk_parent.winfo_x()}+{self.tk_parent.winfo_y()}') self.tk_window.deiconify() self.tk_window.grab_set() @@ -432,14 +493,16 @@ class ProgressBarWindow: self.withdrawn = False - if g_taskbar: g_taskbar.SetProgressValue(self.hwnd, cur_n, self.total) + if g_taskbar: + g_taskbar.SetProgressValue(self.hwnd, cur_n, self.total) else: + #assert self.pbar is not None n_div = (float(n) / self.divider) self.pbar.update(n_div) self.n = cur_n - def end(self): + def end(self) -> None: self.n = 0 self.total = 0 self.divider = 1 @@ -450,6 +513,8 @@ class ProgressBarWindow: self.elapsed_time = 0 if self.tk_window: + #assert self.tk_pbar is not None + if g_taskbar: g_taskbar.SetProgressState(self.hwnd, g_tlb.TBPF_NOPROGRESS) g_taskbar.UnregisterTab(self.hwnd) @@ -461,27 +526,31 @@ class ProgressBarWindow: self.tk_pbar.configure(maximum=100, mode='indeterminate') else: + #assert self.pbar is not None self.pbar.close() self.pbar = None print() - def set_prefix(self, prefix): + def set_prefix(self, prefix) -> None: self.prefix = prefix -def utilsGetPath(path_arg, fallback_path, is_file, create=False): +g_progressBarWindow: Optional[ProgressBarWindow] = None + +def utilsGetPath(path_arg: str, fallback_path: str, is_file: bool, create: bool = False) -> str: path = os.path.abspath(os.path.expanduser(os.path.expandvars(path_arg if path_arg else fallback_path))) - if not is_file and create: os.makedirs(path, exist_ok=True) + if not is_file and create: + os.makedirs(path, exist_ok=True) if not os.path.exists(path) or (is_file and os.path.isdir(path)) or (not is_file and os.path.isfile(path)): - raise Exception("Error: '%s' points to an invalid file/directory." % (path)) + raise Exception(f'Error: "{path}" points to an invalid file/directory.') return path -def utilsIsValueAlignedToEndpointPacketSize(value): +def utilsIsValueAlignedToEndpointPacketSize(value: int) -> bool: return bool((value & (g_usbEpMaxPacketSize - 1)) == 0) -def utilsResetNspInfo(): +def utilsResetNspInfo() -> None: global g_nspTransferMode, g_nspSize, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath # Reset NSP transfer mode info. @@ -490,14 +559,14 @@ def utilsResetNspInfo(): g_nspHeaderSize = 0 g_nspRemainingSize = 0 g_nspFile = None - g_nspFilePath = None + g_nspFilePath = '' -def utilsGetSizeUnitAndDivisor(size): +def utilsGetSizeUnitAndDivisor(size: int) -> Tuple[str, int]: size_suffixes = [ 'B', 'KiB', 'MiB', 'GiB' ] size_suffixes_count = len(size_suffixes) float_size = float(size) - ret = None + ret = (size_suffixes[0], 1) for i in range(size_suffixes_count): if (float_size < pow(1024, i + 1)) or ((i + 1) >= size_suffixes_count): @@ -506,15 +575,19 @@ def utilsGetSizeUnitAndDivisor(size): return ret -def usbGetDeviceEndpoints(): +def usbGetDeviceEndpoints() -> bool: global g_usbEpIn, g_usbEpOut, g_usbEpMaxPacketSize + #assert g_logger is not None + #assert g_stopEvent is not None + prev_dev = cur_dev = None usb_ep_in_lambda = lambda ep: usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN usb_ep_out_lambda = lambda ep: usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_OUT usb_version = 0 - if g_cliMode: g_logger.info('Please connect a Nintendo Switch console running nxdumptool.') + if g_cliMode: + g_logger.info(f'Please connect a Nintendo Switch console running {USB_DEV_PRODUCT}.') while True: # Check if the user decided to stop the server. @@ -523,8 +596,9 @@ def usbGetDeviceEndpoints(): return False # Find a connected USB device with a matching VID/PID pair. + # Using == here to compare both device instances would also compare the backend, so we'll just compare certain elements manually. cur_dev = usb.core.find(idVendor=USB_DEV_VID, idProduct=USB_DEV_PID) - if (cur_dev is None) or ((prev_dev is not None) and (cur_dev.bus == prev_dev.bus) and (cur_dev.address == prev_dev.address)): # Using == here would also compare the backend. + if (cur_dev is None) or ((prev_dev is not None) and (cur_dev.bus == prev_dev.bus) and (cur_dev.address == prev_dev.address)): time.sleep(0.1) continue @@ -532,9 +606,10 @@ def usbGetDeviceEndpoints(): prev_dev = cur_dev # Check if the product and manufacturer strings match the ones used by nxdumptool. + # TODO: enable product string check whenever we're ready for a release. #if (cur_dev.manufacturer != USB_DEV_MANUFACTURER) or (cur_dev.product != USB_DEV_PRODUCT): if cur_dev.manufacturer != USB_DEV_MANUFACTURER: - g_logger.error('Invalid manufacturer/product strings! (bus %u, address %u).' % (cur_dev.bus, cur_dev.address)) + g_logger.error(f'Invalid manufacturer/product strings! (bus {cur_dev.bus}, address {cur_dev.address}).') time.sleep(0.1) continue @@ -553,7 +628,7 @@ def usbGetDeviceEndpoints(): g_usbEpOut = usb.util.find_descriptor(intf, custom_match=usb_ep_out_lambda) if (g_usbEpIn is None) or (g_usbEpOut is None): - g_logger.error('Invalid endpoint addresses! (bus %u, address %u).' % (cur_dev.bus, cur_dev.address)) + g_logger.error(f'Invalid endpoint addresses! (bus {cur_dev.bus}, address {cur_dev.address}).') time.sleep(0.1) continue @@ -563,81 +638,103 @@ def usbGetDeviceEndpoints(): break - g_logger.debug('Successfully retrieved USB endpoints! (bus %u, address %u).' % (cur_dev.bus, cur_dev.address)) - g_logger.debug('Max packet size: 0x%X (USB %u.%u).\n' % (g_usbEpMaxPacketSize, usb_version >> 8, (usb_version & 0xFF) >> 4)) + g_logger.debug(f'Successfully retrieved USB endpoints! (bus {cur_dev.bus}, address {cur_dev.address}).') + g_logger.debug(f'Max packet size: 0x{g_usbEpMaxPacketSize:X} (USB {usb_version >> 8}.{(usb_version & 0xFF) >> 4}).\n') - if g_cliMode: g_logger.info('Exit nxdumptool or disconnect your console at any time to close this script.') + if g_cliMode: + g_logger.info(f'Exit {USB_DEV_PRODUCT} or disconnect your console at any time to close this script.') return True -def usbRead(size, timeout=-1): - rd = None +def usbRead(size: int, timeout: int = -1) -> bytes: + #assert g_logger is not None + + rd = b'' try: # Convert read data to a bytes object for easier handling. rd = bytes(g_usbEpIn.read(size, timeout)) except usb.core.USBError: - if not g_cliMode: traceback.print_exc() + if not g_cliMode: + traceback.print_exc(file=sys.stderr) g_logger.error('\nUSB timeout triggered or console disconnected.') return rd -def usbWrite(data, timeout=-1): +def usbWrite(data: bytes, timeout: int = -1) -> int: + #assert g_logger is not None + wr = 0 try: wr = g_usbEpOut.write(data, timeout) except usb.core.USBError: - if not g_cliMode: traceback.print_exc() + if not g_cliMode: + traceback.print_exc(file=sys.stderr) g_logger.error('\nUSB timeout triggered or console disconnected.') return wr -def usbSendStatus(code): +def usbSendStatus(code: int) -> bool: status = struct.pack('<4sIH6p', USB_MAGIC_WORD, code, g_usbEpMaxPacketSize, b'') - return usbWrite(status, USB_TRANSFER_TIMEOUT) == len(status) + return bool(usbWrite(status, USB_TRANSFER_TIMEOUT) == len(status)) -def usbHandleStartSession(cmd_block): - global g_nxdtVersionMajor, g_nxdtVersionMinor, g_nxdtVersionMicro, g_nxdtAbiVersion, g_nxdtGitCommit +def usbHandleStartSession(cmd_block: bytes) -> int: + global g_nxdtVersionMajor, g_nxdtVersionMinor, g_nxdtVersionMicro, g_nxdtAbiVersionMajor, g_nxdtAbiVersionMinor, g_nxdtGitCommit - if g_cliMode: print() - g_logger.debug('Received StartSession (%02X) command.' % (USB_CMD_START_SESSION)) + #assert g_logger is not None + + if g_cliMode: + print() + + g_logger.debug(f'Received StartSession ({USB_CMD_START_SESSION:02X}) command.') # Parse command block. - (g_nxdtVersionMajor, g_nxdtVersionMinor, g_nxdtVersionMicro, g_nxdtAbiVersion, g_nxdtGitCommit) = struct.unpack_from('> 4) & 0x0F) + g_nxdtAbiVersionMinor = (abi_version & 0x0F) # Print client info. - g_logger.info('Client info: nxdumptool v%u.%u.%u, ABI v%u (commit %s).\n' % (g_nxdtVersionMajor, g_nxdtVersionMinor, g_nxdtVersionMicro, g_nxdtAbiVersion, g_nxdtGitCommit)) + g_logger.info(f'Client info: {USB_DEV_PRODUCT} v{g_nxdtVersionMajor}.{g_nxdtVersionMinor}.{g_nxdtVersionMicro}, USB ABI v{g_nxdtAbiVersionMajor}.{g_nxdtAbiVersionMinor} (commit {g_nxdtGitCommit}).\n') # Check if we support this ABI version. - if g_nxdtAbiVersion != USB_ABI_VERSION: + if (g_nxdtAbiVersionMajor != USB_ABI_VERSION_MAJOR) or (g_nxdtAbiVersionMinor != USB_ABI_VERSION_MINOR): g_logger.error('Unsupported ABI version!') return USB_STATUS_UNSUPPORTED_ABI_VERSION - # Return status code + # Return status code. return USB_STATUS_SUCCESS -def usbHandleSendFileProperties(cmd_block): +def usbHandleSendFileProperties(cmd_block: bytes) -> int | None: global g_nspTransferMode, g_nspSize, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath, g_outputDir, g_tkRoot, g_progressBarWindow - if g_cliMode and not g_nspTransferMode: print() - g_logger.debug('Received SendFileProperties (%02X) command.' % (USB_CMD_SEND_FILE_PROPERTIES)) + #assert g_logger is not None + #assert g_progressBarWindow is not None + + if g_cliMode and not g_nspTransferMode: + print() + + g_logger.debug(f'Received SendFileProperties ({USB_CMD_SEND_FILE_PROPERTIES:02X}) command.') # Parse command block. - (file_size, filename_length, nsp_header_size, raw_filename) = struct.unpack_from(' 0: dbg_str += (' | NSP header size: 0x%X' % (nsp_header_size)) - dbg_str += '.' - g_logger.debug(dbg_str) + dbg_str = f'File size: 0x{file_size:X} | Filename length: 0x{filename_length:X}' + if nsp_header_size > 0: + dbg_str += f' | NSP header size: 0x{nsp_header_size:X}' + g_logger.debug(dbg_str + '.') file_type_str = ('file' if (not g_nspTransferMode) else 'NSP file entry') - if not g_cliMode or (g_cliMode and not g_nspTransferMode): g_logger.info('Receiving %s: "%s".' % (file_type_str, filename)) - # Perform integrity checks + if not g_cliMode or (g_cliMode and not g_nspTransferMode): + g_logger.info(f'Receiving {file_type_str}: "{filename}".') + + # Perform validity checks. if (not g_nspTransferMode) and file_size and (nsp_header_size >= file_size): g_logger.error('NSP header size must be smaller than the full NSP size!\n') return USB_STATUS_MALFORMED_CMD @@ -657,10 +754,10 @@ def usbHandleSendFileProperties(cmd_block): g_nspHeaderSize = nsp_header_size g_nspRemainingSize = (file_size - nsp_header_size) g_nspFile = None - g_nspFilePath = None + g_nspFilePath = '' g_logger.debug('NSP transfer mode enabled!\n') - # Perform additional integrity checks and get a file object to work with. + # Perform additional validity checks and get a file object to work with. if (not g_nspTransferMode) or (g_nspFile is None): # Generate full, absolute path to the destination file. fullpath = os.path.abspath(g_outputDir + os.path.sep + filename) @@ -674,7 +771,7 @@ def usbHandleSendFileProperties(cmd_block): # Make sure the output filepath doesn't point to an existing directory. if os.path.exists(fullpath) and (not os.path.isfile(fullpath)): utilsResetNspInfo() - g_logger.error('Output filepath points to an existing directory! ("%s").\n' % (fullpath)) + g_logger.error(f'Output filepath points to an existing directory! ("{fullpath}").\n') return USB_STATUS_HOST_IO_ERROR # Make sure we have enough free space. @@ -705,7 +802,8 @@ def usbHandleSendFileProperties(cmd_block): # Check if we're dealing with an empty file or with the first SendFileProperties command from a NSP. if (not file_size) or (g_nspTransferMode and file_size == g_nspSize): # Close file (if needed). - if not g_nspTransferMode: file.close() + if not g_nspTransferMode: + file.close() # Let the command handler take care of sending the status response for us. return USB_STATUS_SUCCESS @@ -714,7 +812,7 @@ def usbHandleSendFileProperties(cmd_block): usbSendStatus(USB_STATUS_SUCCESS) # Start data transfer stage. - g_logger.debug('Data transfer started. Saving %s to: "%s".' % (file_type_str, fullpath)) + g_logger.debug(f'Data transfer started. Saving {file_type_str} to: "{fullpath}".') offset = 0 blksize = USB_TRANSFER_BLOCK_SIZE @@ -729,7 +827,7 @@ def usbHandleSendFileProperties(cmd_block): idx = filename.rfind(os.path.sep) prefix_filename = (filename[idx+1:] if (idx >= 0) else filename) - prefix = ('Current %s: "%s".\n' % (file_type_str, prefix_filename)) + prefix = f'Current {file_type_str}: "{prefix_filename}".\n' prefix += 'Use your console to cancel the file transfer if you wish to do so.' if (not g_nspTransferMode) or g_nspRemainingSize == (g_nspSize - g_nspHeaderSize): @@ -756,7 +854,8 @@ def usbHandleSendFileProperties(cmd_block): file.close() os.remove(fullpath) utilsResetNspInfo() - if use_pbar: g_progressBarWindow.end() + if use_pbar: + g_progressBarWindow.end() # Start transfer process. start_time = time.time() @@ -768,12 +867,13 @@ def usbHandleSendFileProperties(cmd_block): # Set block size and handle Zero-Length Termination packet (if needed). rd_size = blksize - if ((offset + blksize) >= file_size) and utilsIsValueAlignedToEndpointPacketSize(blksize): rd_size += 1 + if ((offset + blksize) >= file_size) and utilsIsValueAlignedToEndpointPacketSize(blksize): + rd_size += 1 # Read current chunk. chunk = usbRead(rd_size, USB_TRANSFER_TIMEOUT) - if chunk is None: - g_logger.error('Failed to read 0x%X-byte long data chunk!' % (rd_size)) + if not chunk: + g_logger.error(f'Failed to read 0x{rd_size:X}-byte long data chunk!') # Cancel file transfer. cancelTransfer() @@ -790,7 +890,7 @@ def usbHandleSendFileProperties(cmd_block): # Cancel file transfer. cancelTransfer() - g_logger.debug('Received CancelFileTransfer (%02X) command.' % (USB_CMD_CANCEL_FILE_TRANSFER)) + g_logger.debug(f'Received CancelFileTransfer ({USB_CMD_CANCEL_FILE_TRANSFER:02X}) command.') g_logger.warning('Transfer cancelled.') # Let the command handler take care of sending the status response for us. @@ -804,40 +904,47 @@ def usbHandleSendFileProperties(cmd_block): offset = (offset + chunk_size) # Update remaining NSP data size. - if g_nspTransferMode: g_nspRemainingSize -= chunk_size + if g_nspTransferMode: + g_nspRemainingSize -= chunk_size # Update progress bar window (if needed). - if use_pbar: g_progressBarWindow.update(chunk_size) + if use_pbar: + g_progressBarWindow.update(chunk_size) elapsed_time = round(time.time() - start_time) - g_logger.debug('File transfer successfully completed in %s!\n' % (tqdm.format_interval(elapsed_time))) + g_logger.debug(f'File transfer successfully completed in {tqdm.format_interval(elapsed_time)}!\n') # Close file handle (if needed). - if not g_nspTransferMode: file.close() + if not g_nspTransferMode: + file.close() # Hide progress bar window (if needed). - if use_pbar and ((not g_nspTransferMode) or (not g_nspRemainingSize)): g_progressBarWindow.end() + if use_pbar and ((not g_nspTransferMode) or (not g_nspRemainingSize)): + g_progressBarWindow.end() return USB_STATUS_SUCCESS -def usbHandleSendNspHeader(cmd_block): +def usbHandleSendNspHeader(cmd_block: bytes) -> int: global g_nspTransferMode, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath + #assert g_logger is not None + #assert g_nspFile is not None + nsp_header_size = len(cmd_block) - g_logger.debug('Received SendNspHeader (%02X) command.' % (USB_CMD_SEND_NSP_HEADER)) + g_logger.debug(f'Received SendNspHeader ({USB_CMD_SEND_NSP_HEADER:02X}) command.') - # Integrity checks. + # Validity checks. if not g_nspTransferMode: g_logger.error('Received NSP header out of NSP transfer mode!\n') return USB_STATUS_MALFORMED_CMD if g_nspRemainingSize: - g_logger.error('Received NSP header before receiving all NSP data! (missing 0x%X byte[s]).\n' % (g_nspRemainingSize)) + g_logger.error(f'Received NSP header before receiving all NSP data! (missing 0x{g_nspRemainingSize:X} byte[s]).\n') return USB_STATUS_MALFORMED_CMD if nsp_header_size != g_nspHeaderSize: - g_logger.error('NSP header size mismatch! (0x%X != 0x%X).\n' % (nsp_header_size, g_nspHeaderSize)) + g_logger.error(f'NSP header size mismatch! (0x{nsp_header_size:X} != 0x{g_nspHeaderSize:X}).\n') return USB_STATUS_MALFORMED_CMD # Write NSP header. @@ -845,18 +952,21 @@ def usbHandleSendNspHeader(cmd_block): g_nspFile.write(cmd_block) g_nspFile.close() - g_logger.debug('Successfully wrote 0x%X-byte long NSP header to "%s".\n' % (nsp_header_size, g_nspFilePath)) + g_logger.debug(f'Successfully wrote 0x{nsp_header_size:X}-byte long NSP header to "{g_nspFilePath}".\n') # Disable NSP transfer mode. utilsResetNspInfo() return USB_STATUS_SUCCESS -def usbHandleEndSession(cmd_block): - g_logger.debug('Received EndSession (%02X) command.' % (USB_CMD_END_SESSION)) +def usbHandleEndSession(cmd_block: bytes) -> int: + #assert g_logger is not None + g_logger.debug(f'Received EndSession ({USB_CMD_END_SESSION:02X}) command.') return USB_STATUS_SUCCESS -def usbCommandHandler(): +def usbCommandHandler() -> None: + #assert g_logger is not None + # CancelFileTransfer is handled in usbHandleSendFileProperties(). cmd_dict = { USB_CMD_START_SESSION: usbHandleStartSession, @@ -874,6 +984,8 @@ def usbCommandHandler(): if not g_cliMode: # Update UI. + #assert g_tkCanvas is not None + #assert g_tkServerButton is not None g_tkCanvas.itemconfigure(g_tkTipMessage, state='normal', text=SERVER_STOP_MSG) g_tkServerButton.configure(state='disabled') @@ -883,8 +995,8 @@ def usbCommandHandler(): while True: # Read command header. cmd_header = usbRead(USB_CMD_HEADER_SIZE) - if (cmd_header is None) or (len(cmd_header) != USB_CMD_HEADER_SIZE): - g_logger.error('Failed to read 0x%X-byte long command header!' % (USB_CMD_HEADER_SIZE)) + if (not cmd_header) or (len(cmd_header) != USB_CMD_HEADER_SIZE): + g_logger.error(f'Failed to read 0x{USB_CMD_HEADER_SIZE:X}-byte long command header!') break # Parse command header. @@ -892,7 +1004,7 @@ def usbCommandHandler(): # Read command block right away (if needed). # nxdumptool expects us to read it right after sending the command header. - cmd_block = None + cmd_block: bytes = b'' if cmd_block_size: # Handle Zero-Length Termination packet (if needed). if utilsIsValueAlignedToEndpointPacketSize(cmd_block_size): @@ -901,8 +1013,8 @@ def usbCommandHandler(): rd_size = cmd_block_size cmd_block = usbRead(rd_size, USB_TRANSFER_TIMEOUT) - if (cmd_block is None) or (len(cmd_block) != cmd_block_size): - g_logger.error('Failed to read 0x%X-byte long command block for command ID %02X!' % (cmd_block_size, cmd_id)) + if (not cmd_block) or (len(cmd_block) != cmd_block_size): + g_logger.error(f'Failed to read 0x{cmd_block_size:X}-byte long command block for command ID {cmd_id:02X}!') break # Verify magic word. @@ -914,7 +1026,7 @@ def usbCommandHandler(): # Get command handler function. cmd_func = cmd_dict.get(cmd_id, None) if cmd_func is None: - g_logger.error('Received command header with unsupported ID %02X.\n' % (cmd_id)) + g_logger.error(f'Received command header with unsupported ID {cmd_id:02X}.\n') usbSendStatus(USB_STATUS_UNSUPPORTED_CMD) continue @@ -922,7 +1034,7 @@ def usbCommandHandler(): if (cmd_id == USB_CMD_START_SESSION and cmd_block_size != USB_CMD_BLOCK_SIZE_START_SESSION) or \ (cmd_id == USB_CMD_SEND_FILE_PROPERTIES and cmd_block_size != USB_CMD_BLOCK_SIZE_SEND_FILE_PROPERTIES) or \ (cmd_id == USB_CMD_SEND_NSP_HEADER and not cmd_block_size): - g_logger.error('Invalid command block size for command ID %02X! (0x%X).\n' % (cmd_id, cmd_block_size)) + g_logger.error(f'Invalid command block size for command ID {cmd_id:02X}! (0x{cmd_block_size:X}).\n') usbSendStatus(USB_STATUS_MALFORMED_CMD) continue @@ -938,13 +1050,16 @@ def usbCommandHandler(): # Update UI. uiToggleElements(True) -def uiStopServer(): +def uiStopServer() -> None: # Signal the shared stop event. + #assert g_stopEvent is not None g_stopEvent.set() -def uiStartServer(): +def uiStartServer() -> None: global g_outputDir + #assert g_tkDirText is not None + g_outputDir = g_tkDirText.get('1.0', tk.END).strip() if not g_outputDir: # We should never reach this, honestly. @@ -955,7 +1070,7 @@ def uiStartServer(): try: os.makedirs(g_outputDir, exist_ok=True) except: - traceback.print_exc() + traceback.print_exc(file=sys.stderr) messagebox.showerror('Error', 'Unable to create full output directory tree!', parent=g_tkRoot) return @@ -966,14 +1081,21 @@ def uiStartServer(): server_thread = threading.Thread(target=usbCommandHandler, daemon=True) server_thread.start() -def uiToggleElements(enable): - if enable: +def uiToggleElements(flag: bool) -> None: + #assert g_tkRoot is not None + #assert g_tkChooseDirButton is not None + #assert g_tkServerButton is not None + #assert g_tkCanvas is not None + + if flag: g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocol) g_tkChooseDirButton.configure(state='normal') g_tkServerButton.configure(text='Start server', command=uiStartServer, state='normal') g_tkCanvas.itemconfigure(g_tkTipMessage, state='hidden', text='') else: + #assert g_tkScrolledTextLog is not None + g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocolStub) g_tkChooseDirButton.configure(state='disabled') @@ -984,26 +1106,29 @@ def uiToggleElements(enable): g_tkScrolledTextLog.delete('1.0', tk.END) g_tkScrolledTextLog.configure(state='disabled') -def uiChooseDirectory(): +def uiChooseDirectory() -> None: dir = filedialog.askdirectory(parent=g_tkRoot, title='Select an output directory', initialdir=INITIAL_DIR, mustexist=True) - if dir: uiUpdateDirectoryField(os.path.abspath(dir)) + if dir: + uiUpdateDirectoryField(os.path.abspath(dir)) -def uiUpdateDirectoryField(dir): +def uiUpdateDirectoryField(path: str) -> None: + #assert g_tkDirText is not None g_tkDirText.configure(state='normal') g_tkDirText.delete('1.0', tk.END) - g_tkDirText.insert('1.0', dir) + g_tkDirText.insert('1.0', path) g_tkDirText.configure(state='disabled') -def uiHandleExitProtocol(): +def uiHandleExitProtocol() -> None: + #assert g_tkRoot is not None g_tkRoot.destroy() -def uiHandleExitProtocolStub(): +def uiHandleExitProtocolStub() -> None: pass -def uiScaleMeasure(measure): +def uiScaleMeasure(measure: int) -> int: return round(float(measure) * SCALE) -def uiInitialize(): +def uiInitialize() -> None: global SCALE global g_tkRoot, g_tkCanvas, g_tkDirText, g_tkChooseDirButton, g_tkServerButton, g_tkTipMessage, g_tkScrolledTextLog global g_stopEvent, g_tlb, g_taskbar, g_progressBarWindow @@ -1012,17 +1137,18 @@ def uiInitialize(): g_stopEvent = threading.Event() # Enable high DPI scaling under Windows (if possible). + # This will remove the blur caused by bilineal filtering when automatic scaling is carried out by Windows itself. dpi_aware = False if g_isWindowsVista: try: import ctypes dpi_aware = (ctypes.windll.user32.SetProcessDPIAware() == 1) - if not dpi_aware: dpi_aware = (ctypes.windll.shcore.SetProcessDpiAwareness(1) == 0) + if not dpi_aware: + dpi_aware = (ctypes.windll.shcore.SetProcessDpiAwareness(1) == 0) except: - traceback.print_exc() + traceback.print_exc(file=sys.stderr) # Enable taskbar features under Windows (if possible). - g_tlb = g_taskbar = None del_tlb = False if g_isWindows7: @@ -1039,22 +1165,23 @@ def uiInitialize(): g_taskbar = cc.CreateObject('{56FDF344-FD6D-11D0-958A-006097C9A090}', interface=g_tlb.ITaskbarList3) g_taskbar.HrInit() except: - traceback.print_exc() + traceback.print_exc(file=sys.stderr) - if del_tlb: os.remove(TASKBAR_LIB_PATH) + if del_tlb: + os.remove(TASKBAR_LIB_PATH) # Create root Tkinter object. - g_tkRoot = tk.Tk() + g_tkRoot = tk.Tk(className=SCRIPT_TITLE) g_tkRoot.title(SCRIPT_TITLE) g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocol) g_tkRoot.resizable(False, False) # Set window icon. try: - icon_image = tk.PhotoImage(data=APP_ICON) + icon_image = tk.PhotoImage(data=base64.b64decode(APP_ICON)) g_tkRoot.wm_iconphoto(True, icon_image) except: - traceback.print_exc() + traceback.print_exc(file=sys.stderr) # Get screen resolution. screen_width_px = g_tkRoot.winfo_screenwidth() @@ -1064,16 +1191,28 @@ def uiInitialize(): screen_dpi = round(g_tkRoot.winfo_fpixels('1i')) # Update scaling factor (if needed). - if g_isWindowsVista and dpi_aware: SCALE = (float(screen_dpi) / WINDOWS_SCALING_FACTOR) + if g_isWindowsVista and dpi_aware: + SCALE = (float(screen_dpi) / WINDOWS_SCALING_FACTOR) # Determine window size. window_width_px = uiScaleMeasure(WINDOW_WIDTH) window_height_px = uiScaleMeasure(WINDOW_HEIGHT) + # Retrieve and configure the default font. + default_font = font.nametofont('TkDefaultFont') + default_font_family = ('Segoe UI' if g_isWindows else 'sans-serif') + default_font_size = (-12 if g_isWindows else -10) # Measured in pixels. Reference: https://docs.python.org/3/library/tkinter.font.html + default_font.configure(family=default_font_family, size=uiScaleMeasure(default_font_size), weight=font.NORMAL) + + """print(screen_width_px, screen_height_px) + print(screen_dpi) + print(window_width_px, window_height_px) + print(default_font.cget('family'), default_font.cget('size'))""" + # Center window. pos_hor = int((screen_width_px / 2) - (window_width_px / 2)) pos_ver = int((screen_height_px / 2) - (window_height_px / 2)) - g_tkRoot.geometry("{}x{}+{}+{}".format(window_width_px, window_height_px, pos_hor, pos_ver)) + g_tkRoot.geometry(f'{window_width_px}x{window_height_px}+{pos_hor}+{pos_ver}') # Create canvas and fill it with window elements. g_tkCanvas = tk.Canvas(g_tkRoot, width=window_width_px, height=window_height_px) @@ -1081,7 +1220,7 @@ def uiInitialize(): g_tkCanvas.create_text(uiScaleMeasure(60), uiScaleMeasure(30), text='Output directory:', anchor=tk.CENTER) - g_tkDirText = tk.Text(g_tkRoot, height=1, width=45, font=font.nametofont('TkDefaultFont'), wrap='none', state='disabled', bg='#F0F0F0') + g_tkDirText = tk.Text(g_tkRoot, height=1, width=45, font=default_font, wrap='none', state='disabled', bg='#F0F0F0') uiUpdateDirectoryField(g_outputDir) g_tkCanvas.create_window(uiScaleMeasure(260), uiScaleMeasure(30), window=g_tkDirText, anchor=tk.CENTER) @@ -1089,18 +1228,18 @@ def uiInitialize(): g_tkCanvas.create_window(uiScaleMeasure(450), uiScaleMeasure(30), window=g_tkChooseDirButton, anchor=tk.CENTER) g_tkServerButton = tk.Button(g_tkRoot, text='Start server', width=15, command=uiStartServer) - g_tkCanvas.create_window(uiScaleMeasure(WINDOW_WIDTH / 2), uiScaleMeasure(70), window=g_tkServerButton, anchor=tk.CENTER) + g_tkCanvas.create_window(uiScaleMeasure(int(WINDOW_WIDTH / 2)), uiScaleMeasure(70), window=g_tkServerButton, anchor=tk.CENTER) - g_tkTipMessage = g_tkCanvas.create_text(uiScaleMeasure(WINDOW_WIDTH / 2), uiScaleMeasure(100), anchor=tk.CENTER) + g_tkTipMessage = g_tkCanvas.create_text(uiScaleMeasure(int(WINDOW_WIDTH / 2)), uiScaleMeasure(100), anchor=tk.CENTER) g_tkCanvas.itemconfigure(g_tkTipMessage, state='hidden', text='') - g_tkScrolledTextLog = scrolledtext.ScrolledText(g_tkRoot, height=20, width=65, font=font.nametofont('TkDefaultFont'), wrap=tk.WORD, state='disabled') + g_tkScrolledTextLog = scrolledtext.ScrolledText(g_tkRoot, height=20, width=65, font=default_font, wrap=tk.WORD, state='disabled') g_tkScrolledTextLog.tag_config('DEBUG', foreground='gray') g_tkScrolledTextLog.tag_config('INFO', foreground='black') g_tkScrolledTextLog.tag_config('WARNING', foreground='orange') g_tkScrolledTextLog.tag_config('ERROR', foreground='red') - g_tkScrolledTextLog.tag_config('CRITICAL', foreground='red', underline=1) - g_tkCanvas.create_window(uiScaleMeasure(WINDOW_WIDTH / 2), uiScaleMeasure(280), window=g_tkScrolledTextLog, anchor=tk.CENTER) + g_tkScrolledTextLog.tag_config('CRITICAL', foreground='red', underline=True) + g_tkCanvas.create_window(uiScaleMeasure(int(WINDOW_WIDTH / 2)), uiScaleMeasure(280), window=g_tkScrolledTextLog, anchor=tk.CENTER) g_tkCanvas.create_text(uiScaleMeasure(5), uiScaleMeasure(WINDOW_HEIGHT - 10), text=COPYRIGHT_TEXT, anchor=tk.W) @@ -1115,9 +1254,11 @@ def uiInitialize(): g_tkRoot.lift() g_tkRoot.mainloop() -def cliInitialize(): +def cliInitialize() -> None: global g_progressBarWindow + #assert g_logger is not None + # Initialize console logger. console = LogConsole() @@ -1132,7 +1273,7 @@ def cliInitialize(): # Start USB command handler directly. usbCommandHandler() -def main(): +def main() -> int: global g_cliMode, g_outputDir, g_osType, g_osVersion, g_isWindows, g_isWindowsVista, g_isWindows7, g_logger # Disable warnings. @@ -1178,13 +1319,20 @@ def main(): # Initialize UI. uiInitialize() + return 0 + if __name__ == "__main__": + ret: int = 1 + try: - main() + ret = main() except KeyboardInterrupt: - if g_cliMode: - print('\nScript interrupted.') - try: - sys.exit(0) - except SystemExit: - os._exit(0) + time.sleep(0.2) + print('\nScript interrupted.') + except Exception as e: + traceback.print_exc(file=sys.stderr) + + try: + sys.exit(ret) + except SystemExit: + os._exit(ret) diff --git a/include/core/gamecard.h b/include/core/gamecard.h index 90020c8..4fba7b5 100644 --- a/include/core/gamecard.h +++ b/include/core/gamecard.h @@ -174,22 +174,19 @@ typedef enum { } GameCardCompatibilityType; /// Encrypted using AES-128-CBC with the XCI header key (found in FS program memory under HOS 9.0.0+) and the IV from `GameCardHeader`. -/// Key hashes for documentation purposes: -/// Production XCI header key hash: 2E36CC55157A351090A73E7AE77CF581F69B0B6E48FB066C984879A6ED7D2E96 -/// Development XCI header key hash: 61D5C02244188810E2E3DE69341AC0F3C7653D370C6D3F77CA82B0B7E59F39AD typedef struct { - u64 fw_version; ///< GameCardFwVersion. - u32 acc_ctrl_1; ///< GameCardAccCtrl1. - u32 wait_1_time_read; ///< Always 0x1388. - u32 wait_2_time_read; ///< Always 0. - u32 wait_1_time_write; ///< Always 0. - u32 wait_2_time_write; ///< Always 0. - SdkAddOnVersion fw_mode; ///< Current SdkAddOnVersion. - Version upp_version; ///< Bundled system update version. - u8 compatibility_type; ///< GameCardCompatibilityType. + u64 fw_version; ///< GameCardFwVersion. + u32 acc_ctrl_1; ///< GameCardAccCtrl1. + u32 wait_1_time_read; ///< Always 0x1388. + u32 wait_2_time_read; ///< Always 0. + u32 wait_1_time_write; ///< Always 0. + u32 wait_2_time_write; ///< Always 0. + Version fw_mode; ///< Current SDK version. + Version upp_version; ///< Bundled system update version. + u8 compatibility_type; ///< GameCardCompatibilityType. u8 reserved_1[0x3]; - u64 upp_hash; ///< SHA-256 (?) checksum for the update partition. The exact way it's calculated is currently unknown. - u64 upp_id; ///< Must match GAMECARD_UPDATE_TID. + u64 upp_hash; ///< Checksum for the update partition. The exact way it's calculated is currently unknown. + u64 upp_id; ///< Must match GAMECARD_UPDATE_TID. u8 reserved_2[0x38]; } GameCardInfo; @@ -233,16 +230,6 @@ typedef enum { GameCardStatus_InsertedAndInfoLoaded = 5 ///< A gamecard has been inserted and all required information could be successfully retrieved from it. } GameCardStatus; -typedef enum { - GameCardHashFileSystemPartitionType_None = 0, ///< Not a real value. - GameCardHashFileSystemPartitionType_Root = 1, - GameCardHashFileSystemPartitionType_Update = 2, - GameCardHashFileSystemPartitionType_Logo = 3, ///< Only available in GameCardFwVersion_Since400NUP or greater gamecards. - GameCardHashFileSystemPartitionType_Normal = 4, - GameCardHashFileSystemPartitionType_Secure = 5, - GameCardHashFileSystemPartitionType_Count = 6 ///< Total values supported by this enum. -} GameCardHashFileSystemPartitionType; - typedef enum { LotusAsicFirmwareType_ReadFw = 0xFF, LotusAsicFirmwareType_ReadDevFw = 0xFFFF, @@ -295,7 +282,7 @@ u8 gamecardGetStatus(void); /// Fills the provided GameCardSecurityInformation pointer. /// This area can't be read using gamecardReadStorage(). -bool gamecardGetSecurityInformation(GameCardSecurityInformation* out); +bool gamecardGetSecurityInformation(GameCardSecurityInformation *out); /// Fills the provided FsGameCardIdSet pointer. /// This area can't be read using gamecardReadStorage(). diff --git a/include/core/hfs.h b/include/core/hfs.h index 9ff296b..cd7f7d5 100644 --- a/include/core/hfs.h +++ b/include/core/hfs.h @@ -50,10 +50,20 @@ typedef struct { NXDT_ASSERT(HashFileSystemEntry, 0x40); +typedef enum { + HashFileSystemPartitionType_None = 0, ///< Not a real value. + HashFileSystemPartitionType_Root = 1, + HashFileSystemPartitionType_Update = 2, + HashFileSystemPartitionType_Logo = 3, ///< Only available in GameCardFwVersion_Since400NUP or greater gamecards. + HashFileSystemPartitionType_Normal = 4, + HashFileSystemPartitionType_Secure = 5, + HashFileSystemPartitionType_Count = 6 ///< Total values supported by this enum. +} HashFileSystemPartitionType; + /// Internally used by gamecard functions. /// Use gamecardGetHashFileSystemContext() to retrieve a Hash FS context. typedef struct { - u8 type; ///< GameCardHashFileSystemPartitionType. + u8 type; ///< HashFileSystemPartitionType. char *name; ///< Dynamically allocated partition name. u64 offset; ///< Partition offset (relative to the start of gamecard image). u64 size; ///< Partition size. @@ -61,9 +71,6 @@ typedef struct { u8 *header; ///< HashFileSystemHeader + (HashFileSystemEntry * entry_count) + Name Table. } HashFileSystemContext; -/// Retrieves a Hash FS entry index by its name. -bool hfsGetEntryIndexByName(HashFileSystemContext *ctx, const char *name, u32 *out_idx); - /// Reads raw partition data using a Hash FS context. /// Input offset must be relative to the start of the Hash FS. bool hfsReadPartitionData(HashFileSystemContext *ctx, void *out, u64 read_size, u64 offset); @@ -73,8 +80,16 @@ bool hfsReadPartitionData(HashFileSystemContext *ctx, void *out, u64 read_size, bool hfsReadEntryData(HashFileSystemContext *ctx, HashFileSystemEntry *fs_entry, void *out, u64 read_size, u64 offset); /// Calculates the extracted Hash FS size. +/// If the target partition is empty, 'out_size' will be set to zero and true will be returned. bool hfsGetTotalDataSize(HashFileSystemContext *ctx, u64 *out_size); +/// Retrieves a Hash FS entry index by its name. +bool hfsGetEntryIndexByName(HashFileSystemContext *ctx, const char *name, u32 *out_idx); + +/// Takes a HashFileSystemPartitionType value. Returns a pointer to a string that represents the partition name that matches the provided Hash FS partition type. +/// Returns NULL if the provided value is out of range. +const char *hfsGetPartitionNameString(u8 hfs_partition_type); + /// Miscellaneous functions. NX_INLINE void hfsFreeContext(HashFileSystemContext *ctx) @@ -85,6 +100,11 @@ NX_INLINE void hfsFreeContext(HashFileSystemContext *ctx) memset(ctx, 0, sizeof(HashFileSystemContext)); } +NX_INLINE bool hfsIsValidContext(HashFileSystemContext *ctx) +{ + return (ctx && ctx->type > HashFileSystemPartitionType_None && ctx->type < HashFileSystemPartitionType_Count && ctx->name && ctx->size && ctx->header_size && ctx->header); +} + NX_INLINE u32 hfsGetEntryCount(HashFileSystemContext *ctx) { if (!ctx || !ctx->header_size || !ctx->header) return 0; diff --git a/include/core/nca.h b/include/core/nca.h index 0ac4dd5..daa7d8a 100644 --- a/include/core/nca.h +++ b/include/core/nca.h @@ -92,7 +92,7 @@ typedef enum { NcaKeyGeneration_Since1300NUP = 13, ///< 13.0.0 - 13.2.1. NcaKeyGeneration_Since1400NUP = 14, ///< 14.0.0 - 14.1.2. NcaKeyGeneration_Since1500NUP = 15, ///< 15.0.0 - 15.0.1. - NcaKeyGeneration_Since1600NUP = 16, ///< 16.0.0 - 16.0.1. + NcaKeyGeneration_Since1600NUP = 16, ///< 16.0.0 - 16.0.3. NcaKeyGeneration_Current = NcaKeyGeneration_Since1600NUP, NcaKeyGeneration_Max = 32 } NcaKeyGeneration; @@ -108,7 +108,7 @@ typedef enum { /// TODO: update on signature keygen changes. typedef enum { NcaSignatureKeyGeneration_Since100NUP = 0, ///< 1.0.0 - 8.1.1. - NcaSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0 - 16.0.1. + NcaSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0 - 16.0.3. NcaSignatureKeyGeneration_Current = NcaSignatureKeyGeneration_Since900NUP, NcaSignatureKeyGeneration_Max = (NcaSignatureKeyGeneration_Current + 1) } NcaSignatureKeyGeneration; @@ -158,7 +158,7 @@ typedef struct { u64 content_size; u64 program_id; u32 content_index; - SdkAddOnVersion sdk_addon_version; + Version sdk_addon_version; u8 key_generation; ///< NcaKeyGeneration. Uses NcaKeyGeneration_Since301NUP or greater values. u8 main_signature_key_generation; ///< NcaSignatureKeyGeneration. u8 reserved[0xE]; @@ -483,7 +483,7 @@ bool ncaAllocateCryptoBuffer(void); void ncaFreeCryptoBuffer(void); /// Initializes a NCA context. -/// If 'storage_id' == NcmStorageId_GameCard, the 'hfs_partition_type' argument must be a valid GameCardHashFileSystemPartitionType value. +/// If 'storage_id' == NcmStorageId_GameCard, the 'hfs_partition_type' argument must be a valid HashFileSystemPartitionType value. /// If the NCA holds a populated Rights ID field, ticket data will need to be retrieved. /// If the 'tik' argument points to a valid Ticket element, it will either be updated (if it's empty) or used to read ticket data that has already been retrieved. /// If the 'tik' argument is NULL, the function will just retrieve the necessary ticket data on its own. diff --git a/include/core/npdm.h b/include/core/npdm.h index b774f9c..8b5ef45 100644 --- a/include/core/npdm.h +++ b/include/core/npdm.h @@ -43,7 +43,7 @@ extern "C" { /// TODO: update on signature keygen changes. typedef enum { NpdmSignatureKeyGeneration_Since100NUP = 0, ///< 1.0.0 - 8.1.1. - NpdmSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0 - 16.0.1. + NpdmSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0 - 16.0.3. NpdmSignatureKeyGeneration_Current = NpdmSignatureKeyGeneration_Since900NUP, NpdmSignatureKeyGeneration_Max = (NpdmSignatureKeyGeneration_Current + 1) } NpdmSignatureKeyGeneration; diff --git a/include/core/nxdt_includes.h b/include/core/nxdt_includes.h index e957722..ba6b415 100644 --- a/include/core/nxdt_includes.h +++ b/include/core/nxdt_includes.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -76,10 +77,8 @@ #define LZ4_STATIC_LINKING_ONLY /* Required by LZ4 to enable in-place decompression. */ #include "lz4.h" -/// Used to store version numbers expressed in dot notation: -/// * System version: "{major}.{minor}.{micro}-{major_relstep}.{minor_relstep}". -/// * Application version: "{release}.{private}". -/// Referenced by multiple header files. +/// Used to store version numbers expressed in dot notation: "{major}.{minor}.{micro}-{major_relstep}.{minor_relstep}". +/// Used by system version fields. typedef struct { union { u32 value; @@ -89,18 +88,28 @@ typedef struct { u32 micro : 4; u32 minor : 6; u32 major : 6; - } system_version; - struct { - u32 private_ver : 16; - u32 release_ver : 16; - } application_version; + }; }; -} Version; +} SystemVersion; -NXDT_ASSERT(Version, 0x4); +NXDT_ASSERT(SystemVersion, 0x4); + +/// Used to store version numbers expressed in dot notation: "{release}.{private}". +/// Used by application version fields. +typedef struct { + union { + u32 value; + struct { + u32 private_ver : 16; + u32 release_ver : 16; + }; + }; +} ApplicationVersion; + +NXDT_ASSERT(ApplicationVersion, 0x4); /// Used to store version numbers expressed in dot notation: "{major}.{minor}.{micro}-{relstep}". -/// Only used by GameCardFwMode and NcaSdkAddOnVersion. +/// Used by SDK version fields. This format was also used for system version fields prior to HOS 3.0.0. typedef struct { union { u32 value; @@ -115,4 +124,16 @@ typedef struct { NXDT_ASSERT(SdkAddOnVersion, 0x4); +/// Convenient wrapper for all version structs. +typedef struct { + union { + u32 value; + SystemVersion system_version; + ApplicationVersion application_version; + SdkAddOnVersion sdk_addon_version; + }; +} Version; + +NXDT_ASSERT(Version, 0x4); + #endif /* __NXDT_INCLUDES_H__ */ diff --git a/include/core/nxdt_utils.h b/include/core/nxdt_utils.h index 1feb5c9..be5036f 100644 --- a/include/core/nxdt_utils.h +++ b/include/core/nxdt_utils.h @@ -149,6 +149,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); +/// 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); + /// Returns a pointer to a dynamically allocated string that holds the full path formed by the provided arguments. Both path prefix and file extension are optional. /// If any elements from the generated path exceed safe filesystem limits, each exceeding element will be truncated. Truncations, if needed, are performed on a per-codepoint basis (UTF-8). /// If an extension is provided, it will always be preserved, regardless of any possible truncations being carried out. diff --git a/include/core/romfs.h b/include/core/romfs.h index 590f233..cb777a2 100644 --- a/include/core/romfs.h +++ b/include/core/romfs.h @@ -193,6 +193,7 @@ NX_INLINE void romfsFreeContext(RomFileSystemContext *ctx) } /// Functions to reset the current directory/file entry offset. + NX_INLINE void romfsResetDirectoryTableOffset(RomFileSystemContext *ctx) { if (ctx) ctx->cur_dir_offset = 0; @@ -211,6 +212,7 @@ NX_INLINE bool romfsIsValidContext(RomFileSystemContext *ctx) } /// Functions to retrieve a directory/file entry. + NX_INLINE void *romfsGetEntryByOffset(RomFileSystemContext *ctx, void *entry_table, u64 entry_table_size, u64 entry_size, u64 entry_offset) { if (!romfsIsValidContext(ctx) || !entry_table || !entry_table_size || !entry_size || (entry_offset + entry_size) > entry_table_size) return NULL; @@ -238,6 +240,7 @@ NX_INLINE RomFileSystemFileEntry *romfsGetCurrentFileEntry(RomFileSystemContext } /// Functions to check if it's possible to move to the next directory/file entry based on the current directory/file entry offset. + NX_INLINE bool romfsCanMoveToNextEntry(RomFileSystemContext *ctx, void *entry_table, u64 entry_table_size, u64 entry_size, u64 entry_offset) { if (!romfsIsValidContext(ctx) || !entry_table || !entry_table_size || entry_size < 4 || (entry_offset + entry_size) > entry_table_size) return false; @@ -256,6 +259,7 @@ NX_INLINE bool romfsCanMoveToNextFileEntry(RomFileSystemContext *ctx) } /// Functions to update the current directory/file entry offset to make it point to the next directory/file entry. + NX_INLINE bool romfsMoveToNextEntry(RomFileSystemContext *ctx, void *entry_table, u64 entry_table_size, u64 entry_size, u64 *entry_offset) { if (!romfsIsValidContext(ctx) || !entry_table || !entry_table_size || entry_size < 4 || !entry_offset || (*entry_offset + entry_size) > entry_table_size) return false; @@ -275,6 +279,7 @@ NX_INLINE bool romfsMoveToNextFileEntry(RomFileSystemContext *ctx) } /// NCA patch management functions. + NX_INLINE void romfsWriteFileEntryPatchToMemoryBuffer(RomFileSystemContext *ctx, RomFileSystemFileEntryPatch *patch, void *buf, u64 buf_size, u64 buf_offset) { if (!romfsIsValidContext(ctx) || ctx->is_patch || ctx->default_storage_ctx->base_storage_type != NcaStorageBaseStorageType_Regular || !patch || \ diff --git a/include/core/rsa.h b/include/core/rsa.h index 6dd0c85..51581dc 100644 --- a/include/core/rsa.h +++ b/include/core/rsa.h @@ -36,13 +36,13 @@ extern "C" { #define RSA2048_PUBKEY_SIZE RSA2048_BYTES /// Verifies a RSA-2048-PSS with SHA-256 signature. -/// The provided signature and modulus should have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively. +/// The provided signature and modulus must have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively. bool rsa2048VerifySha256BasedPssSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size); /// Performs RSA-2048-OAEP decryption. /// Suitable to decrypt the titlekey block from tickets with personalized crypto. -/// The provided signature and modulus should have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively. -/// The label and label_size arguments are optional - these may be set to NULL and 0 if not needed, respectively. +/// The provided signature and modulus must have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively. +/// 'label' and 'label_size' arguments are optional -- if not needed, these may be set to NULL and 0, respectively. bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, const void *private_exponent, \ size_t private_exponent_size, const void *label, size_t label_size, size_t *out_size); diff --git a/include/core/tik.h b/include/core/tik.h index 955d36a..bac2132 100644 --- a/include/core/tik.h +++ b/include/core/tik.h @@ -34,13 +34,11 @@ extern "C" { #define SIGNED_TIK_MIN_SIZE sizeof(TikSigHmac160) /* Assuming no ESV1/ESV2 records are available. */ #define GENERATE_TIK_STRUCT(sigtype, tiksize) \ -\ typedef struct { \ SignatureBlock##sigtype sig_block; \ TikCommonBlock tik_common_block; \ u8 es_section_record_data[]; \ } TikSig##sigtype; \ -\ NXDT_ASSERT(TikSig##sigtype, tiksize); typedef enum { @@ -178,9 +176,10 @@ typedef struct { /// Titlekey is also RSA-OAEP unwrapped (if needed) and titlekek decrypted right away. bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gamecard); -/// Converts a TikTitleKeyType_Personalized ticket into a TikTitleKeyType_Common ticket and generates a raw certificate chain for the new signature issuer. +/// Converts a TikTitleKeyType_Personalized ticket into a TikTitleKeyType_Common ticket and optionally generates a raw certificate chain for the new signature issuer. /// Bear in mind the 'size' member from the Ticket parameter will be updated by this function to remove any possible references to ESV1/ESV2 records. -/// Raw certificate chain data will be saved to the provided pointers. certGenerateRawCertificateChainBySignatureIssuer() is used internally, so the output buffer must be freed by the user. +/// If both 'out_raw_cert_chain' and 'out_raw_cert_chain_size' pointers are provided, raw certificate chain data will be saved to them. +/// certGenerateRawCertificateChainBySignatureIssuer() is used internally, so the output buffer must be freed by the user. bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_chain, u64 *out_raw_cert_chain_size); /// Helper inline functions. diff --git a/include/core/usb.h b/include/core/usb.h index 1abb8a8..cbe5c50 100644 --- a/include/core/usb.h +++ b/include/core/usb.h @@ -54,32 +54,32 @@ void *usbAllocatePageAlignedBuffer(size_t size); /// Returns a value from the UsbHostSpeed enum. u8 usbIsReady(void); -/// Sends file properties to the host device before starting a file data transfer. Must be called before usbSendFileData(). -/// If 'nsp_header_size' is greater than zero, NSP transfer mode will be enabled. The file will be treated as a NSP and this value will be taken as its full Partition FS header size. +/// Sends file properties to the host device before starting a file data transfer. If needed, it must be called before usbSendFileData(). +/// 'file_size' may be zero if an empty file shall be created, in which case no file data transfer will be necessary. +/// Calling this function before finishing an ongoing file data transfer will result in an error. /// Under NSP transfer mode, this function must be called right before transferring data from each NSP file entry to the host device, which should in turn write it all to the same output file. -/// Calling this function after NSP transfer mode has been enabled with a 'nsp_header_size' value greater than zero will result in an error. -/// The host device should immediately write 'nsp_header_size' padding at the start of the output file and start listening for further usbSendFileProperties() calls, or a usbSendNspHeader() call. -bool usbSendFileProperties(u64 file_size, const char *filename, u32 nsp_header_size); +bool usbSendFileProperties(u64 file_size, const char *filename); -/// Performs a file data transfer. Must be continuously called after usbSendFileProperties() until all file data has been transferred. +/// Sends NSP properties to the host device and enables NSP transfer mode. If needed, it must be called before usbSendFileData(). +/// Both 'nsp_size' and 'nsp_header_size' must be greater than zero. 'nsp_size' must also be greater than 'nsp_header_size'. +/// Calling this function after NSP transfer mode has already been enabled will result in an error. +/// The host device should immediately write 'nsp_header_size' padding at the start of the output file and start listening for further usbSendFileProperties() calls, or a usbSendNspHeader() call. +bool usbSendNspProperties(u64 nsp_size, const char *filename, u32 nsp_header_size); + +/// Performs a file data transfer. Must be continuously called after usbSendFileProperties() / usbSendNspProperties() until all file data has been transferred. /// Data chunk size must not exceed USB_TRANSFER_BUFFER_SIZE. /// If the last file data chunk is aligned to the endpoint max packet size, the host device should expect a Zero Length Termination (ZLT) packet. +/// Calling this function if there's no remaining data to transfer will result in an error. bool usbSendFileData(void *data, u64 data_size); /// Used to gracefully cancel an ongoing file transfer. The current USB session is kept alive. void usbCancelFileTransfer(void); /// Sends NSP header data to the host device, making it rewind the NSP file pointer to write this data, essentially finishing the NSP transfer process. -/// Must be called after the data from all NSP file entries has been transferred using both usbSendFileProperties() and usbSendFileData() calls. +/// Must be called after the data from all NSP file entries has been transferred using both usbSendNspProperties() and usbSendFileData() calls. /// If the NSP header size is aligned to the endpoint max packet size, the host device should expect a Zero Length Termination (ZLT) packet. bool usbSendNspHeader(void *nsp_header, u32 nsp_header_size); -/// Nice and small wrapper for non-NSP files. -NX_INLINE bool usbSendFilePropertiesCommon(u64 file_size, const char *filename) -{ - return usbSendFileProperties(file_size, filename, 0); -} - #ifdef __cplusplus } #endif diff --git a/include/defines.h b/include/defines.h index c682c28..a48fb99 100644 --- a/include/defines.h +++ b/include/defines.h @@ -87,7 +87,7 @@ #define NCA_PATH APP_BASE_PATH "NCA/" #define NCA_FS_PATH APP_BASE_PATH "NCA FS/" -#define CONFIG_PATH DEVOPTAB_SDMC_DEVICE APP_BASE_PATH "config.json" +#define CONFIG_FILE_NAME APP_TITLE "_config.json" #define DEFAULT_CONFIG_PATH "romfs:/default_config.json" #define NRO_NAME APP_TITLE ".nro" diff --git a/include/fatfs/ff.h b/include/fatfs/ff.h index e0a7712..7d29ce7 100644 --- a/include/fatfs/ff.h +++ b/include/fatfs/ff.h @@ -221,7 +221,7 @@ typedef struct { -/* Directory object structure (DIR) */ +/* Directory object structure (FDIR) */ typedef struct { FFOBJID obj; /* Object identifier */ @@ -236,7 +236,7 @@ typedef struct { #if FF_USE_FIND const TCHAR* pat; /* Pointer to the name matching pattern */ #endif -} DIR; +} FDIR; @@ -308,11 +308,11 @@ FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data t FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */ FRESULT f_truncate (FIL* fp); /* Truncate the file */ FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */ -FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */ -FRESULT f_closedir (DIR* dp); /* Close an open directory */ -FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */ -FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */ -FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */ +FRESULT f_opendir (FDIR* dp, const TCHAR* path); /* Open a directory */ +FRESULT f_closedir (FDIR* dp); /* Close an open directory */ +FRESULT f_readdir (FDIR* dp, FILINFO* fno); /* Read a directory item */ +FRESULT f_findfirst (FDIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */ +FRESULT f_findnext (FDIR* dp, FILINFO* fno); /* Find next file */ FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */ FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */ FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */ diff --git a/libs/borealis b/libs/borealis index f9f4aa9..2112222 160000 --- a/libs/borealis +++ b/libs/borealis @@ -1 +1 @@ -Subproject commit f9f4aa9637f84aa89025d68a8ec15e5ff34d1537 +Subproject commit 21122223a496b313c2d45cb02cabfc0e4680e7e7 diff --git a/romfs/default_config.json b/romfs/default_config.json index 7d1582b..7ca0806 100644 --- a/romfs/default_config.json +++ b/romfs/default_config.json @@ -7,7 +7,8 @@ "keep_certificate": false, "trim_dump": false, "calculate_checksum": true, - "checksum_lookup_method": 1 + "checksum_lookup_method": 1, + "write_raw_hfs_partition": false }, "nsp": { "set_download_distribution": false, diff --git a/romfs/i18n/en-US/gamecard_tab.json b/romfs/i18n/en-US/gamecard_tab.json index a3d3354..cc69084 100644 --- a/romfs/i18n/en-US/gamecard_tab.json +++ b/romfs/i18n/en-US/gamecard_tab.json @@ -24,7 +24,8 @@ "lafw_version": "Required LAFW version", "lafw_version_value": "%lu or greater (%s)", "sdk_version": "SDK version", - "compatibility_type": "Compatibility type" + "compatibility_type": "Compatibility type", + "package_id": "Package ID" }, "dump_options": "Dump options", diff --git a/source/core/bfttf.c b/source/core/bfttf.c index 553064b..555f939 100644 --- a/source/core/bfttf.c +++ b/source/core/bfttf.c @@ -82,13 +82,14 @@ bool bfttfInitialize(void) for(u32 i = 0; i < g_fontInfoCount; i++) { BfttfFontInfo *font_info = &(g_fontInfo[i]); - TitleInfo *title_info = NULL; RomFileSystemFileEntry *romfs_file_entry = NULL; /* Check if the title ID for the current font container matches the one from the previous font container. */ /* We won't have to reinitialize both NCA and RomFS contexts if that's the case. */ if (font_info->title_id != prev_title_id) { + TitleInfo *title_info = NULL; + /* Get title info. */ if (!(title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, font_info->title_id))) { @@ -98,8 +99,9 @@ bool bfttfInitialize(void) /* Initialize NCA context. */ /* NCA contexts don't need to be freed beforehand. */ - bool nca_ctx_init = ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), \ - title_info->version.value, NULL); + /* Don't allow invalid NCA signatures. */ + bool nca_ctx_init = (ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), \ + title_info->version.value, NULL) && nca_ctx->valid_main_signature); /* Free title info. */ titleFreeTitleInfo(&title_info); diff --git a/source/core/cert.c b/source/core/cert.c index dcfb95b..3869177 100644 --- a/source/core/cert.c +++ b/source/core/cert.c @@ -141,7 +141,7 @@ u8 *certRetrieveRawCertificateChainFromGameCardByRightsId(const FsRightsId *id, utilsGenerateHexStringFromData(raw_chain_filename, sizeof(raw_chain_filename), id->c, sizeof(id->c), false); strcat(raw_chain_filename, ".cert"); - if (!gamecardGetHashFileSystemEntryInfoByName(GameCardHashFileSystemPartitionType_Secure, raw_chain_filename, &raw_chain_offset, &raw_chain_size)) + if (!gamecardGetHashFileSystemEntryInfoByName(HashFileSystemPartitionType_Secure, raw_chain_filename, &raw_chain_offset, &raw_chain_size)) { LOG_MSG_ERROR("Error retrieving offset and size for \"%s\" entry in secure hash FS partition!", raw_chain_filename); return NULL; diff --git a/source/core/config.c b/source/core/config.c index 4baacb7..56fb53c 100644 --- a/source/core/config.c +++ b/source/core/config.c @@ -60,6 +60,7 @@ void configSet##functype(const char *path, vartype value) { \ static Mutex g_configMutex = 0; static bool g_configInterfaceInit = false; +static char g_configJsonPath[FS_MAX_PATH] = {0}; static struct json_object *g_configJson = NULL; /* Function prototypes. */ @@ -118,10 +119,29 @@ CONFIG_SETTER(Integer, int); static bool configParseConfigJson(void) { - bool use_default_config = true, ret = false; + bool use_default_config = true, use_root = true, ret = false; + const char *launch_path = utilsGetLaunchPath(); + char *ptr1 = NULL, *ptr2 = NULL; + + /* Generate config JSON path. */ + if (launch_path) + { + ptr1 = strchr(launch_path, '/'); + ptr2 = strrchr(launch_path, '/'); + + if (ptr1 && ptr2 && ptr1 != ptr2) + { + /* Use config JSON from the current working directory. */ + snprintf(g_configJsonPath, sizeof(g_configJsonPath), "%.*s" CONFIG_FILE_NAME, (int)((ptr2 - launch_path) + 1), launch_path); + use_root = false; + } + } + + /* Use config JSON from the SD card root directory. */ + if (use_root) sprintf(g_configJsonPath, DEVOPTAB_SDMC_DEVICE "/" CONFIG_FILE_NAME); /* Read config JSON. */ - g_configJson = json_object_from_file(CONFIG_PATH); + g_configJson = json_object_from_file(g_configJsonPath); if (g_configJson) { /* Validate configuration. */ @@ -157,7 +177,7 @@ static bool configParseConfigJson(void) static void configWriteConfigJson(void) { if (!g_configJson) return; - if (json_object_to_file_ext(CONFIG_PATH, g_configJson, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) != 0) jsonLogLastError(); + if (json_object_to_file_ext(g_configJsonPath, g_configJson, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) != 0) jsonLogLastError(); } static void configFreeConfigJson(void) @@ -194,7 +214,8 @@ end: static bool configValidateJsonGameCardObject(const struct json_object *obj) { - bool ret = false, prepend_key_area_found = false, keep_certificate_found = false, trim_dump_found = false, calculate_checksum_found = false, checksum_lookup_method_found = false; + bool ret = false, prepend_key_area_found = false, keep_certificate_found = false, trim_dump_found = false, calculate_checksum_found = false; + bool checksum_lookup_method_found = false, write_raw_hfs_partition_found = false; if (!jsonValidateObject(obj)) goto end; @@ -205,10 +226,11 @@ static bool configValidateJsonGameCardObject(const struct json_object *obj) CONFIG_VALIDATE_FIELD(Boolean, trim_dump); CONFIG_VALIDATE_FIELD(Boolean, calculate_checksum); CONFIG_VALIDATE_FIELD(Integer, checksum_lookup_method, ConfigChecksumLookupMethod_None, ConfigChecksumLookupMethod_Count - 1); + CONFIG_VALIDATE_FIELD(Boolean, write_raw_hfs_partition); goto end; } - ret = (prepend_key_area_found && keep_certificate_found && trim_dump_found && calculate_checksum_found && checksum_lookup_method_found); + ret = (prepend_key_area_found && keep_certificate_found && trim_dump_found && calculate_checksum_found && checksum_lookup_method_found && write_raw_hfs_partition_found); end: return ret; @@ -217,7 +239,8 @@ end: static bool configValidateJsonNspObject(const struct json_object *obj) { bool ret = false, set_download_distribution_found = false, remove_console_data_found = false, remove_titlekey_crypto_found = false; - bool disable_linked_account_requirement_found = false, enable_screenshots_found = false, enable_video_capture_found = false, disable_hdcp_found = false, append_authoringtool_data_found = false, lookup_checksum_found = false; + bool disable_linked_account_requirement_found = false, enable_screenshots_found = false, enable_video_capture_found = false, disable_hdcp_found = false; + bool append_authoringtool_data_found = false, lookup_checksum_found = false; if (!jsonValidateObject(obj)) goto end; diff --git a/source/core/gamecard.c b/source/core/gamecard.c index 1814d02..bca5fc9 100644 --- a/source/core/gamecard.c +++ b/source/core/gamecard.c @@ -35,8 +35,6 @@ #define GAMECARD_STORAGE_AREA_NAME(x) ((x) == GameCardStorageArea_Normal ? "normal" : ((x) == GameCardStorageArea_Secure ? "secure" : "none")) -#define GAMECARD_HFS_PARTITION_NAME_INDEX(x) ((x) - 1) - #define LAFW_MAGIC 0x4C414657 /* "LAFW". */ /* Type definitions. */ @@ -95,14 +93,6 @@ static MemoryLocation g_fsProgramMemory = { .data_size = 0 }; -static const char *g_gameCardHfsPartitionNames[] = { - [GAMECARD_HFS_PARTITION_NAME_INDEX(GameCardHashFileSystemPartitionType_Root)] = "root", - [GAMECARD_HFS_PARTITION_NAME_INDEX(GameCardHashFileSystemPartitionType_Update)] = "update", - [GAMECARD_HFS_PARTITION_NAME_INDEX(GameCardHashFileSystemPartitionType_Logo)] = "logo", - [GAMECARD_HFS_PARTITION_NAME_INDEX(GameCardHashFileSystemPartitionType_Normal)] = "normal", - [GAMECARD_HFS_PARTITION_NAME_INDEX(GameCardHashFileSystemPartitionType_Secure)] = "secure" -}; - static const char *g_gameCardHosVersionStrings[GameCardFwVersion_Count] = { [GameCardFwVersion_ForDev] = "1.0.0", [GameCardFwVersion_Since100NUP] = "1.0.0", @@ -457,7 +447,7 @@ bool gamecardGetBundledFirmwareUpdateVersion(Version *out) bool gamecardGetHashFileSystemContext(u8 hfs_partition_type, HashFileSystemContext *out) { - if (!hfs_partition_type || hfs_partition_type >= GameCardHashFileSystemPartitionType_Count || !out) + if (hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type >= HashFileSystemPartitionType_Count || !out) { LOG_MSG_ERROR("Invalid parameters!"); return false; @@ -471,30 +461,30 @@ bool gamecardGetHashFileSystemContext(u8 hfs_partition_type, HashFileSystemConte SCOPED_LOCK(&g_gameCardMutex) { /* Get pointer to the Hash FS context for the requested partition. */ - HashFileSystemContext *fs_ctx = _gamecardGetHashFileSystemContext(hfs_partition_type); - if (!fs_ctx) break; + HashFileSystemContext *hfs_ctx = _gamecardGetHashFileSystemContext(hfs_partition_type); + if (!hfs_ctx) break; /* Fill Hash FS context. */ - out->name = strdup(fs_ctx->name); + out->name = strdup(hfs_ctx->name); if (!out->name) { - LOG_MSG_ERROR("Failed to duplicate Hash FS partition name! (%s).", fs_ctx->name); + LOG_MSG_ERROR("Failed to duplicate Hash FS partition name! (%s).", hfs_ctx->name); break; } - out->type = fs_ctx->type; - out->offset = fs_ctx->offset; - out->size = fs_ctx->size; - out->header_size = fs_ctx->header_size; + out->type = hfs_ctx->type; + out->offset = hfs_ctx->offset; + out->size = hfs_ctx->size; + out->header_size = hfs_ctx->header_size; - out->header = calloc(fs_ctx->header_size, sizeof(u8)); + out->header = calloc(hfs_ctx->header_size, sizeof(u8)); if (!out->header) { - LOG_MSG_ERROR("Failed to duplicate Hash FS partition header! (%s).", fs_ctx->name); + LOG_MSG_ERROR("Failed to duplicate Hash FS partition header! (%s).", hfs_ctx->name); break; } - memcpy(out->header, fs_ctx->header, fs_ctx->header_size); + memcpy(out->header, hfs_ctx->header, hfs_ctx->header_size); /* Update flag. */ ret = true; @@ -507,7 +497,7 @@ bool gamecardGetHashFileSystemContext(u8 hfs_partition_type, HashFileSystemConte bool gamecardGetHashFileSystemEntryInfoByName(u8 hfs_partition_type, const char *entry_name, u64 *out_offset, u64 *out_size) { - if (!hfs_partition_type || hfs_partition_type >= GameCardHashFileSystemPartitionType_Count || !entry_name || !*entry_name || (!out_offset && !out_size)) + if (hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type >= HashFileSystemPartitionType_Count || !entry_name || !*entry_name || (!out_offset && !out_size)) { LOG_MSG_ERROR("Invalid parameters!"); return false; @@ -518,16 +508,16 @@ bool gamecardGetHashFileSystemEntryInfoByName(u8 hfs_partition_type, const char SCOPED_LOCK(&g_gameCardMutex) { /* Get pointer to the Hash FS context for the requested partition. */ - HashFileSystemContext *fs_ctx = _gamecardGetHashFileSystemContext(hfs_partition_type); - if (!fs_ctx) break; + HashFileSystemContext *hfs_ctx = _gamecardGetHashFileSystemContext(hfs_partition_type); + if (!hfs_ctx) break; /* Get Hash FS entry by name. */ - HashFileSystemEntry *fs_entry = hfsGetEntryByName(fs_ctx, entry_name); - if (!fs_entry) break; + HashFileSystemEntry *hfs_entry = hfsGetEntryByName(hfs_ctx, entry_name); + if (!hfs_entry) break; /* Update output variables. */ - if (out_offset) *out_offset = (fs_ctx->offset + fs_ctx->header_size + fs_entry->offset); - if (out_size) *out_size = fs_entry->size; + if (out_offset) *out_offset = (hfs_ctx->offset + hfs_ctx->header_size + hfs_entry->offset); + if (out_size) *out_size = hfs_entry->size; /* Update flag. */ ret = true; @@ -734,9 +724,9 @@ static void gamecardLoadInfo(void) { if (g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded) return; - HashFileSystemContext *root_fs_ctx = NULL; - u32 root_fs_entry_count = 0, root_fs_name_table_size = 0; - char *root_fs_name_table = NULL; + HashFileSystemContext *root_hfs_ctx = NULL; + u32 root_hfs_entry_count = 0, root_hfs_name_table_size = 0; + char *root_hfs_name_table = NULL; /* Set initial gamecard status. */ g_gameCardStatus = GameCardStatus_InsertedAndInfoNotLoaded; @@ -781,12 +771,12 @@ static void gamecardLoadInfo(void) } /* Initialize Hash FS context for the root partition. */ - root_fs_ctx = gamecardInitializeHashFileSystemContext(NULL, g_gameCardHeader.partition_fs_header_address, 0, g_gameCardHeader.partition_fs_header_hash, 0, g_gameCardHeader.partition_fs_header_size); - if (!root_fs_ctx) goto end; + root_hfs_ctx = gamecardInitializeHashFileSystemContext(NULL, g_gameCardHeader.partition_fs_header_address, 0, g_gameCardHeader.partition_fs_header_hash, 0, g_gameCardHeader.partition_fs_header_size); + if (!root_hfs_ctx) goto end; /* Calculate total Hash FS partition count. */ - root_fs_entry_count = hfsGetEntryCount(root_fs_ctx); - g_gameCardHfsCount = (root_fs_entry_count + 1); + root_hfs_entry_count = hfsGetEntryCount(root_hfs_ctx); + g_gameCardHfsCount = (root_hfs_entry_count + 1); /* Allocate Hash FS context pointer array. */ g_gameCardHfsCtx = calloc(g_gameCardHfsCount, sizeof(HashFileSystemContext*)); @@ -797,26 +787,26 @@ static void gamecardLoadInfo(void) } /* Set root partition context as the first pointer. */ - g_gameCardHfsCtx[0] = root_fs_ctx; + g_gameCardHfsCtx[0] = root_hfs_ctx; /* Get root partition name table. */ - root_fs_name_table_size = ((HashFileSystemHeader*)root_fs_ctx->header)->name_table_size; - root_fs_name_table = hfsGetNameTable(root_fs_ctx); + root_hfs_name_table_size = ((HashFileSystemHeader*)root_hfs_ctx->header)->name_table_size; + root_hfs_name_table = hfsGetNameTable(root_hfs_ctx); /* Initialize Hash FS contexts for the child partitions. */ - for(u32 i = 0; i < root_fs_entry_count; i++) + for(u32 i = 0; i < root_hfs_entry_count; i++) { - HashFileSystemEntry *fs_entry = hfsGetEntryByIndex(root_fs_ctx, i); - char *fs_entry_name = (root_fs_name_table + fs_entry->name_offset); - u64 fs_entry_offset = (root_fs_ctx->offset + root_fs_ctx->header_size + fs_entry->offset); + HashFileSystemEntry *hfs_entry = hfsGetEntryByIndex(root_hfs_ctx, i); + char *hfs_entry_name = (root_hfs_name_table + hfs_entry->name_offset); + u64 hfs_entry_offset = (root_hfs_ctx->offset + root_hfs_ctx->header_size + hfs_entry->offset); - if (fs_entry->name_offset >= root_fs_name_table_size || !*fs_entry_name) + if (hfs_entry->name_offset >= root_hfs_name_table_size || !*hfs_entry_name) { LOG_MSG_ERROR("Invalid name for root Hash FS partition entry #%u!", i); goto end; } - g_gameCardHfsCtx[i + 1] = gamecardInitializeHashFileSystemContext(fs_entry_name, fs_entry_offset, fs_entry->size, fs_entry->hash, fs_entry->hash_target_offset, fs_entry->hash_target_size); + g_gameCardHfsCtx[i + 1] = gamecardInitializeHashFileSystemContext(hfs_entry_name, hfs_entry_offset, hfs_entry->size, hfs_entry->hash, hfs_entry->hash_target_offset, hfs_entry->hash_target_size); if (!g_gameCardHfsCtx[i + 1]) goto end; } @@ -826,10 +816,10 @@ static void gamecardLoadInfo(void) end: if (g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded) { - if (!g_gameCardHfsCtx && root_fs_ctx) + if (!g_gameCardHfsCtx && root_hfs_ctx) { - hfsFreeContext(root_fs_ctx); - free(root_fs_ctx); + hfsFreeContext(root_hfs_ctx); + free(root_hfs_ctx); } gamecardFreeInfo(false); @@ -850,11 +840,11 @@ static void gamecardFreeInfo(bool clear_status) { for(u32 i = 0; i < g_gameCardHfsCount; i++) { - HashFileSystemContext *cur_fs_ctx = g_gameCardHfsCtx[i]; - if (cur_fs_ctx) + HashFileSystemContext *cur_hfs_ctx = g_gameCardHfsCtx[i]; + if (cur_hfs_ctx) { - hfsFreeContext(cur_fs_ctx); - free(cur_fs_ctx); + hfsFreeContext(cur_hfs_ctx); + free(cur_hfs_ctx); } } @@ -1225,9 +1215,9 @@ NX_INLINE u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size) static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char *name, u64 offset, u64 size, u8 *hash, u64 hash_target_offset, u32 hash_target_size) { u32 i = 0, magic = 0; - HashFileSystemContext *fs_ctx = NULL; - HashFileSystemHeader fs_header = {0}; - u8 fs_header_hash[SHA256_HASH_SIZE] = {0}; + HashFileSystemContext *hfs_ctx = NULL; + HashFileSystemHeader hfs_header = {0}; + u8 hfs_header_hash[SHA256_HASH_SIZE] = {0}; bool success = false, dump_fs_header = false; @@ -1239,149 +1229,150 @@ static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char } /* Allocate memory for the output context. */ - fs_ctx = calloc(1, sizeof(HashFileSystemContext)); - if (!fs_ctx) + hfs_ctx = calloc(1, sizeof(HashFileSystemContext)); + if (!hfs_ctx) { LOG_MSG_ERROR("Unable to allocate memory for Hash FS context! (offset 0x%lX).", offset); goto end; } /* Duplicate partition name. */ - fs_ctx->name = (name ? strdup(name) : strdup(g_gameCardHfsPartitionNames[GAMECARD_HFS_PARTITION_NAME_INDEX(GameCardHashFileSystemPartitionType_Root)])); - if (!fs_ctx->name) + hfs_ctx->name = (name ? strdup(name) : strdup(hfsGetPartitionNameString(HashFileSystemPartitionType_Root))); + if (!hfs_ctx->name) { LOG_MSG_ERROR("Failed to duplicate Hash FS partition name! (offset 0x%lX).", offset); goto end; } /* Determine Hash FS partition type. */ - for(i = GameCardHashFileSystemPartitionType_Root; i < GameCardHashFileSystemPartitionType_Count; i++) + for(i = HashFileSystemPartitionType_Root; i < HashFileSystemPartitionType_Count; i++) { - if (!strcmp(g_gameCardHfsPartitionNames[GAMECARD_HFS_PARTITION_NAME_INDEX(i)], fs_ctx->name)) break; + const char *hfs_partition_name = hfsGetPartitionNameString((u8)i); + if (hfs_partition_name && !strcmp(hfs_partition_name, hfs_ctx->name)) break; } - if (i >= GameCardHashFileSystemPartitionType_Count) + if (i >= HashFileSystemPartitionType_Count) { - LOG_MSG_ERROR("Failed to find a matching Hash FS partition type for \"%s\"! (offset 0x%lX).", fs_ctx->name, offset); + LOG_MSG_ERROR("Failed to find a matching Hash FS partition type for \"%s\"! (offset 0x%lX).", hfs_ctx->name, offset); goto end; } - fs_ctx->type = i; + hfs_ctx->type = i; /* Read partial Hash FS header. */ - if (!gamecardReadStorageArea(&fs_header, sizeof(HashFileSystemHeader), offset)) + if (!gamecardReadStorageArea(&hfs_header, sizeof(HashFileSystemHeader), offset)) { - LOG_MSG_ERROR("Failed to read partial Hash FS header! (\"%s\", offset 0x%lX).", fs_ctx->name, offset); + LOG_MSG_ERROR("Failed to read partial Hash FS header! (\"%s\", offset 0x%lX).", hfs_ctx->name, offset); goto end; } - magic = __builtin_bswap32(fs_header.magic); + magic = __builtin_bswap32(hfs_header.magic); if (magic != HFS0_MAGIC) { - LOG_MSG_ERROR("Invalid Hash FS magic word! (0x%08X) (\"%s\", offset 0x%lX).", magic, fs_ctx->name, offset); + LOG_MSG_ERROR("Invalid Hash FS magic word! (0x%08X) (\"%s\", offset 0x%lX).", magic, hfs_ctx->name, offset); dump_fs_header = true; goto end; } /* Check Hash FS entry count and name table size. */ /* Only allow a zero entry count if we're not dealing with the root partition. Never allow a zero-sized name table. */ - if ((!name && !fs_header.entry_count) || !fs_header.name_table_size) + if ((!name && !hfs_header.entry_count) || !hfs_header.name_table_size) { - LOG_MSG_ERROR("Invalid Hash FS entry count / name table size! (\"%s\", offset 0x%lX).", fs_ctx->name, offset); + LOG_MSG_ERROR("Invalid Hash FS entry count / name table size! (\"%s\", offset 0x%lX).", hfs_ctx->name, offset); dump_fs_header = true; goto end; } /* Calculate full Hash FS header size. */ - fs_ctx->header_size = (sizeof(HashFileSystemHeader) + (fs_header.entry_count * sizeof(HashFileSystemEntry)) + fs_header.name_table_size); - fs_ctx->header_size = ALIGN_UP(fs_ctx->header_size, GAMECARD_PAGE_SIZE); + hfs_ctx->header_size = (sizeof(HashFileSystemHeader) + (hfs_header.entry_count * sizeof(HashFileSystemEntry)) + hfs_header.name_table_size); + hfs_ctx->header_size = ALIGN_UP(hfs_ctx->header_size, GAMECARD_PAGE_SIZE); /* Allocate memory for the full Hash FS header. */ - fs_ctx->header = calloc(fs_ctx->header_size, sizeof(u8)); - if (!fs_ctx->header) + hfs_ctx->header = calloc(hfs_ctx->header_size, sizeof(u8)); + if (!hfs_ctx->header) { - LOG_MSG_ERROR("Unable to allocate 0x%lX bytes buffer for the full Hash FS header! (\"%s\", offset 0x%lX).", fs_ctx->header_size, fs_ctx->name, offset); + LOG_MSG_ERROR("Unable to allocate 0x%lX bytes buffer for the full Hash FS header! (\"%s\", offset 0x%lX).", hfs_ctx->header_size, hfs_ctx->name, offset); goto end; } /* Read full Hash FS header. */ - if (!gamecardReadStorageArea(fs_ctx->header, fs_ctx->header_size, offset)) + if (!gamecardReadStorageArea(hfs_ctx->header, hfs_ctx->header_size, offset)) { - LOG_MSG_ERROR("Failed to read full Hash FS header! (\"%s\", offset 0x%lX).", fs_ctx->name, offset); + LOG_MSG_ERROR("Failed to read full Hash FS header! (\"%s\", offset 0x%lX).", hfs_ctx->name, offset); goto end; } /* Verify Hash FS header (if possible). */ - if (hash && hash_target_size && (hash_target_offset + hash_target_size) <= fs_ctx->header_size) + if (hash && hash_target_size && (hash_target_offset + hash_target_size) <= hfs_ctx->header_size) { - sha256CalculateHash(fs_header_hash, fs_ctx->header + hash_target_offset, hash_target_size); - if (memcmp(fs_header_hash, hash, SHA256_HASH_SIZE) != 0) + sha256CalculateHash(hfs_header_hash, hfs_ctx->header + hash_target_offset, hash_target_size); + if (memcmp(hfs_header_hash, hash, SHA256_HASH_SIZE) != 0) { - LOG_MSG_ERROR("Hash FS header doesn't match expected SHA-256 hash! (\"%s\", offset 0x%lX).", fs_ctx->name, offset); + LOG_MSG_ERROR("Hash FS header doesn't match expected SHA-256 hash! (\"%s\", offset 0x%lX).", hfs_ctx->name, offset); goto end; } } /* Fill context. */ - fs_ctx->offset = offset; + hfs_ctx->offset = offset; if (name) { /* Use provided partition size. */ - fs_ctx->size = size; + hfs_ctx->size = size; } else { /* Calculate root partition size. */ - HashFileSystemEntry *fs_entry = hfsGetEntryByIndex(fs_ctx, fs_header.entry_count - 1); - fs_ctx->size = (fs_ctx->header_size + fs_entry->offset + fs_entry->size); + HashFileSystemEntry *hfs_entry = hfsGetEntryByIndex(hfs_ctx, hfs_header.entry_count - 1); + hfs_ctx->size = (hfs_ctx->header_size + hfs_entry->offset + hfs_entry->size); } /* Update flag. */ success = true; end: - if (!success && fs_ctx) + if (!success && hfs_ctx) { - if (dump_fs_header) LOG_DATA_DEBUG(&fs_header, sizeof(HashFileSystemHeader), "Partial Hash FS header dump (\"%s\", offset 0x%lX):", fs_ctx->name, offset); + if (dump_fs_header) LOG_DATA_DEBUG(&hfs_header, sizeof(HashFileSystemHeader), "Partial Hash FS header dump (\"%s\", offset 0x%lX):", hfs_ctx->name, offset); - if (fs_ctx->header) free(fs_ctx->header); + if (hfs_ctx->header) free(hfs_ctx->header); - if (fs_ctx->name) free(fs_ctx->name); + if (hfs_ctx->name) free(hfs_ctx->name); - free(fs_ctx); - fs_ctx = NULL; + free(hfs_ctx); + hfs_ctx = NULL; } - return fs_ctx; + return hfs_ctx; } static HashFileSystemContext *_gamecardGetHashFileSystemContext(u8 hfs_partition_type) { - HashFileSystemContext *fs_ctx = NULL; + HashFileSystemContext *hfs_ctx = NULL; - if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHfsCount || !g_gameCardHfsCtx || !hfs_partition_type || \ - hfs_partition_type >= GameCardHashFileSystemPartitionType_Count) + if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHfsCount || !g_gameCardHfsCtx || \ + hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type >= HashFileSystemPartitionType_Count) { LOG_MSG_ERROR("Invalid parameters!"); goto end; } /* Return right away if the root partition was requested. */ - if (hfs_partition_type == GameCardHashFileSystemPartitionType_Root) + if (hfs_partition_type == HashFileSystemPartitionType_Root) { - fs_ctx = g_gameCardHfsCtx[0]; + hfs_ctx = g_gameCardHfsCtx[0]; goto end; } /* Try to find the requested partition by looping through our Hash FS contexts. */ for(u32 i = 1; i < g_gameCardHfsCount; i++) { - fs_ctx = g_gameCardHfsCtx[i]; - if (fs_ctx->type == hfs_partition_type) break; - fs_ctx = NULL; + hfs_ctx = g_gameCardHfsCtx[i]; + if (hfs_ctx->type == hfs_partition_type) break; + hfs_ctx = NULL; } - if (!fs_ctx) LOG_MSG_ERROR("Failed to locate Hash FS partition with type %u!", hfs_partition_type); + if (!hfs_ctx) LOG_MSG_ERROR("Failed to locate Hash FS partition with type %u!", hfs_partition_type); end: - return fs_ctx; + return hfs_ctx; } diff --git a/source/core/hfs.c b/source/core/hfs.c index ea1e4ca..8de0c31 100644 --- a/source/core/hfs.c +++ b/source/core/hfs.c @@ -22,9 +22,19 @@ #include "nxdt_utils.h" #include "gamecard.h" +#define HFS_PARTITION_NAME_INDEX(x) ((x) - 1) + +static const char *g_hfsPartitionNames[] = { + [HFS_PARTITION_NAME_INDEX(HashFileSystemPartitionType_Root)] = "root", + [HFS_PARTITION_NAME_INDEX(HashFileSystemPartitionType_Update)] = "update", + [HFS_PARTITION_NAME_INDEX(HashFileSystemPartitionType_Logo)] = "logo", + [HFS_PARTITION_NAME_INDEX(HashFileSystemPartitionType_Normal)] = "normal", + [HFS_PARTITION_NAME_INDEX(HashFileSystemPartitionType_Secure)] = "secure" +}; + bool hfsReadPartitionData(HashFileSystemContext *ctx, void *out, u64 read_size, u64 offset) { - if (!ctx || !ctx->size || !out || !read_size || (offset + read_size) > ctx->size) + if (!hfsIsValidContext(ctx) || !out || !read_size || (offset + read_size) > ctx->size) { LOG_MSG_ERROR("Invalid parameters!"); return false; @@ -60,16 +70,16 @@ bool hfsReadEntryData(HashFileSystemContext *ctx, HashFileSystemEntry *fs_entry, bool hfsGetTotalDataSize(HashFileSystemContext *ctx, u64 *out_size) { - u64 total_size = 0; - u32 entry_count = hfsGetEntryCount(ctx); - HashFileSystemEntry *fs_entry = NULL; - - if (!entry_count || !out_size) + if (!hfsIsValidContext(ctx) || !out_size) { LOG_MSG_ERROR("Invalid parameters!"); return false; } + u64 total_size = 0; + u32 entry_count = hfsGetEntryCount(ctx); + HashFileSystemEntry *fs_entry = NULL; + for(u32 i = 0; i < entry_count; i++) { if (!(fs_entry = hfsGetEntryByIndex(ctx, i))) @@ -89,15 +99,24 @@ bool hfsGetTotalDataSize(HashFileSystemContext *ctx, u64 *out_size) bool hfsGetEntryIndexByName(HashFileSystemContext *ctx, const char *name, u32 *out_idx) { HashFileSystemEntry *fs_entry = NULL; - u32 entry_count = hfsGetEntryCount(ctx), name_table_size = 0; - char *name_table = hfsGetNameTable(ctx); + u32 entry_count = 0, name_table_size = 0; + char *name_table = NULL; + bool ret = false; - if (!entry_count || !name_table || !name || !*name || !out_idx) + if (hfsIsValidContext(ctx) && name && *name && out_idx) { - LOG_MSG_ERROR("Invalid parameters!"); - return false; + entry_count = hfsGetEntryCount(ctx); + name_table = hfsGetNameTable(ctx); + ret = (entry_count && name_table); } + if (!ret) + { + LOG_MSG_ERROR("Invalid parameters!"); + goto end; + } + + ret = false; name_table_size = ((HashFileSystemHeader*)ctx->header)->name_table_size; for(u32 i = 0; i < entry_count; i++) @@ -117,9 +136,17 @@ bool hfsGetEntryIndexByName(HashFileSystemContext *ctx, const char *name, u32 *o if (!strcmp(name_table + fs_entry->name_offset, name)) { *out_idx = i; - return true; + ret = true; + break; } } - return false; +end: + return ret; +} + +const char *hfsGetPartitionNameString(u8 hfs_partition_type) +{ + return ((hfs_partition_type > HashFileSystemPartitionType_None && hfs_partition_type < HashFileSystemPartitionType_Count) ? \ + g_hfsPartitionNames[HFS_PARTITION_NAME_INDEX(hfs_partition_type)] : NULL); } diff --git a/source/core/nca.c b/source/core/nca.c index ae00de5..6697b84 100644 --- a/source/core/nca.c +++ b/source/core/nca.c @@ -181,8 +181,8 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, u8 valid_fs_section_cnt = 0; if (!out || (storage_id != NcmStorageId_GameCard && !(ncm_storage = titleGetNcmStorageByStorageId(storage_id))) || \ - (storage_id == NcmStorageId_GameCard && (!hfs_partition_type || hfs_partition_type >= GameCardHashFileSystemPartitionType_Count)) || !content_info || \ - content_info->content_type >= NcmContentType_DeltaFragment) + (storage_id == NcmStorageId_GameCard && (hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type >= HashFileSystemPartitionType_Count)) || \ + !content_info || content_info->content_type >= NcmContentType_DeltaFragment) { LOG_MSG_ERROR("Invalid parameters!"); return false; @@ -279,7 +279,7 @@ bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset) /* This strips NAX0 crypto from SD card NCAs (not used on eMMC NCAs). */ rc = ncmContentStorageReadContentIdFile(ctx->ncm_storage, out, read_size, &(ctx->content_id), offset); ret = R_SUCCEEDED(rc); - if (!ret) LOG_MSG_ERROR("Failed to read 0x%lX bytes block at offset 0x%lX from NCA \"%s\"! (0x%X) (ncm).", read_size, offset, ctx->content_id_str, rc); + if (!ret) LOG_MSG_ERROR("Failed to read 0x%lX bytes block at offset 0x%lX from NCA \"%s\"! (ncm) (0x%X).", read_size, offset, ctx->content_id_str, rc); } else { /* Retrieve NCA data using raw gamecard reads. */ /* Fixes NCA read issues with gamecards under HOS < 4.0.0 when using ncmContentStorageReadContentIdFile(). */ diff --git a/source/core/nxdt_bfsar.c b/source/core/nxdt_bfsar.c index ac9f227..2f7ffca 100644 --- a/source/core/nxdt_bfsar.c +++ b/source/core/nxdt_bfsar.c @@ -67,13 +67,13 @@ bool bfsarInitialize(void) if (ptr1 && ptr2 && ptr1 != ptr2) { /* Create BFSAR file in the current working directory. */ - snprintf(g_bfsarPath, sizeof(g_bfsarPath), "%.*s" BFSAR_FILENAME, (int)((ptr2 - ptr1) + 1), ptr1); + snprintf(g_bfsarPath, sizeof(g_bfsarPath), "%.*s" BFSAR_FILENAME, (int)((ptr2 - launch_path) + 1), launch_path); use_root = false; } } /* Create BFSAR file in the SD card root directory. */ - if (use_root) sprintf(g_bfsarPath, "/" BFSAR_FILENAME); + if (use_root) sprintf(g_bfsarPath, DEVOPTAB_SDMC_DEVICE "/" BFSAR_FILENAME); LOG_MSG_DEBUG("BFSAR path: \"%s\".", g_bfsarPath); @@ -106,7 +106,9 @@ bool bfsarInitialize(void) } /* Initialize NCA context. */ - if (!ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Program, 0), title_info->version.value, NULL)) + /* Don't allow invalid NCA signatures. */ + if (!ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Program, 0), title_info->version.value, NULL) || \ + !nca_ctx->valid_main_signature) { LOG_MSG_ERROR("Failed to initialize qlaunch Program NCA context!"); break; diff --git a/source/core/nxdt_log.c b/source/core/nxdt_log.c index 15f4783..be5f276 100644 --- a/source/core/nxdt_log.c +++ b/source/core/nxdt_log.c @@ -430,13 +430,13 @@ static bool logOpenLogFile(void) if (ptr1 && ptr2 && ptr1 != ptr2) { - /* Create logfile in the current working directory. */ + /* Create logfile in the current working directory. Strip the devoptab device name prefix while we're at it, since we won't need it. */ snprintf(path, sizeof(path), "%.*s" LOG_FILE_NAME, (int)((ptr2 - ptr1) + 1), ptr1); use_root = false; } } - /* Create logfile in the SD card root directory. */ + /* Create logfile in the SD card root directory, if needed. */ if (use_root) sprintf(path, "/" LOG_FILE_NAME); /* Create file. This will fail if the logfile exists, so we don't check its return value. */ @@ -452,17 +452,17 @@ static bool logOpenLogFile(void) { size_t len = 0; - /* Write UTF-8 BOM right away (if needed). */ if (!g_logFileOffset) { + /* Write UTF-8 BOM if the logfile is empty. */ len = strlen(UTF8_BOM); - fsFileWrite(&g_logFile, g_logFileOffset, UTF8_BOM, len, FsWriteOption_Flush); - g_logFileOffset += (s64)len; + rc = fsFileWrite(&g_logFile, g_logFileOffset, UTF8_BOM, len, FsWriteOption_Flush); + } else { + /* Write session separator if the logfile isn't empty. */ + len = strlen(g_logSessionSeparator); + rc = fsFileWrite(&g_logFile, g_logFileOffset, g_logSessionSeparator, len, FsWriteOption_Flush); } - /* Write session separator right away. */ - len = strlen(g_logSessionSeparator); - rc = fsFileWrite(&g_logFile, g_logFileOffset, g_logSessionSeparator, len, FsWriteOption_Flush); if (R_SUCCEEDED(rc)) g_logFileOffset += (s64)len; } } diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index 2d32ed0..a6a18db 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -244,7 +244,13 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv) { bool flag = false; rc = appletIsGamePlayRecordingSupported(&flag); - if (R_SUCCEEDED(rc) && flag) appletInitializeGamePlayRecording(); + if (R_SUCCEEDED(rc) && flag) + { + rc = appletInitializeGamePlayRecording(); + if (R_FAILED(rc)) LOG_MSG_ERROR("appletInitializeGamePlayRecording failed! (0x%X).", rc); + } else { + LOG_MSG_ERROR("appletIsGamePlayRecordingSupported returned [0x%X, %u].", rc, flag); + } } /* Update flags. */ @@ -758,6 +764,86 @@ void utilsCreateDirectoryTree(const char *path, bool create_last_element) free(tmp); } +bool utilsDeleteDirectoryRecursively(const char *path) +{ + char *name_end = NULL, *entry_path = NULL; + DIR *dir = NULL; + struct dirent *entry = NULL; + bool success = true; + + /* Sanity checks. */ + if (!path || !*path || !(name_end = strchr(path, ':')) || *(name_end + 1) != '/' || !*(name_end + 2)) + { + LOG_MSG_ERROR("Invalid parameters!"); + return false; + } + + 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; + } + + /* Read directory entries. */ + while((entry = readdir(dir))) + { + int status = 0; + + /* 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) + { + /* Delete directory contents. */ + if (!utilsDeleteDirectoryRecursively(entry_path)) + { + success = false; + break; + } + + /* Delete directory. */ + status = rmdir(entry_path); + } else { + /* Delete file. */ + status = unlink(entry_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; + } + } + + if (success) + { + closedir(dir); + dir = NULL; + + 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); + + if (dir) closedir(dir); + + return success; +} + char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension) { if (!filename || !*filename) diff --git a/source/core/services.c b/source/core/services.c index daa225e..36f74fe 100644 --- a/source/core/services.c +++ b/source/core/services.c @@ -62,7 +62,8 @@ static ServiceInfo g_serviceInfo[] = { { false, "set", NULL, &setInitialize, &setExit }, { false, "set:sys", NULL, &setsysInitialize, &setsysExit }, { false, "set:cal", NULL, &setcalInitialize, &setcalExit }, - { false, "bsd:u", NULL, &socketInitializeDefault, &socketExit } /* socketInitialize*() functions take care of initializing bsd:* too. */ + { false, "bsd:u", NULL, &socketInitializeDefault, &socketExit }, /* socketInitialize*() functions take care of initializing bsd:* too. */ + { false, "usb:ds", NULL, &usbDsInitialize, &usbDsExit } }; static const u32 g_serviceInfoCount = MAX_ELEMENTS(g_serviceInfo); diff --git a/source/core/tik.c b/source/core/tik.c index e96a9b7..91ee863 100644 --- a/source/core/tik.c +++ b/source/core/tik.c @@ -168,7 +168,7 @@ bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_c u8 *signature = NULL; u64 signature_size = 0; - bool dev_cert = false; + bool generate_cert = false, dev_cert = false; char cert_chain_issuer[0x40] = {0}; static const char *common_cert_names[] = { "XS00000020", "XS00000022", NULL }; @@ -176,26 +176,31 @@ bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_c u64 raw_cert_chain_size = 0; if (!tik || tik->type == TikType_None || tik->type > TikType_SigHmac160 || tik->size < SIGNED_TIK_MIN_SIZE || tik->size > SIGNED_TIK_MAX_SIZE || \ - !(tik_common_block = tikGetCommonBlock(tik->data)) || tik_common_block->titlekey_type != TikTitleKeyType_Personalized || !out_raw_cert_chain || !out_raw_cert_chain_size) + !(tik_common_block = tikGetCommonBlock(tik->data)) || tik_common_block->titlekey_type != TikTitleKeyType_Personalized || (!out_raw_cert_chain && out_raw_cert_chain_size) || \ + (out_raw_cert_chain && !out_raw_cert_chain_size)) { LOG_MSG_ERROR("Invalid parameters!"); return false; } - /* Generate raw certificate chain for the new signature issuer (common). */ - dev_cert = (strstr(tik_common_block->issuer, "CA00000004") != NULL); - - for(u8 i = 0; common_cert_names[i] != NULL; i++) + /* Generate raw certificate chain for the new signature issuer (common), if needed. */ + generate_cert = (out_raw_cert_chain && out_raw_cert_chain_size); + if (generate_cert) { - sprintf(cert_chain_issuer, "Root-CA%08X-%s", dev_cert ? 4 : 3, common_cert_names[i]); - raw_cert_chain = certGenerateRawCertificateChainBySignatureIssuer(cert_chain_issuer, &raw_cert_chain_size); - if (raw_cert_chain) break; - } + dev_cert = (strstr(tik_common_block->issuer, "CA00000004") != NULL); - if (!raw_cert_chain) - { - LOG_MSG_ERROR("Failed to generate raw certificate chain for common ticket signature issuer!"); - return false; + for(u8 i = 0; common_cert_names[i] != NULL; i++) + { + sprintf(cert_chain_issuer, "Root-CA%08X-%s", dev_cert ? 4 : 3, common_cert_names[i]); + raw_cert_chain = certGenerateRawCertificateChainBySignatureIssuer(cert_chain_issuer, &raw_cert_chain_size); + if (raw_cert_chain) break; + } + + if (!raw_cert_chain) + { + LOG_MSG_ERROR("Failed to generate raw certificate chain for common ticket signature issuer!"); + return false; + } } /* Wipe signature. */ @@ -230,8 +235,11 @@ bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_c memset(tik->data + tik->size, 0, SIGNED_TIK_MAX_SIZE - tik->size); /* Update output pointers. */ - *out_raw_cert_chain = raw_cert_chain; - *out_raw_cert_chain_size = raw_cert_chain_size; + if (generate_cert) + { + *out_raw_cert_chain = raw_cert_chain; + *out_raw_cert_chain_size = raw_cert_chain_size; + } return true; } @@ -250,7 +258,7 @@ static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsI utilsGenerateHexStringFromData(tik_filename, sizeof(tik_filename), id->c, sizeof(id->c), false); strcat(tik_filename, ".tik"); - if (!gamecardGetHashFileSystemEntryInfoByName(GameCardHashFileSystemPartitionType_Secure, tik_filename, &tik_offset, &tik_size)) + if (!gamecardGetHashFileSystemEntryInfoByName(HashFileSystemPartitionType_Secure, tik_filename, &tik_offset, &tik_size)) { LOG_MSG_ERROR("Error retrieving offset and size for \"%s\" entry in secure hash FS partition!", tik_filename); return false; diff --git a/source/core/title.c b/source/core/title.c index 989dd50..c0cb848 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -540,6 +540,8 @@ static bool titleRetrieveUserApplicationMetadataByTitleId(u64 title_id, TitleApp NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id, bool is_system, u32 extra_app_count); +NX_INLINE u64 titleGetApplicationIdByMetaKey(const NcmContentMetaKey *meta_key); + static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_storage); static bool titleGetMetaKeysFromContentDatabase(NcmContentMetaDatabase *ncm_db, NcmContentMetaKey **out_meta_keys, u32 *out_meta_key_count); static bool titleGetContentInfosForMetaKey(NcmContentMetaDatabase *ncm_db, const NcmContentMetaKey *meta_key, NcmContentInfo **out_content_infos, u32 *out_content_count); @@ -1777,6 +1779,33 @@ NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 ti return NULL; } +NX_INLINE u64 titleGetApplicationIdByMetaKey(const NcmContentMetaKey *meta_key) +{ + if (!meta_key) return 0; + + u64 app_id = meta_key->id; + + switch(meta_key->type) + { + case NcmContentMetaType_Patch: + app_id = titleGetApplicationIdByPatchId(meta_key->id); + break; + case NcmContentMetaType_AddOnContent: + app_id = titleGetApplicationIdByAddOnContentId(meta_key->id); + break; + case NcmContentMetaType_Delta: + app_id = titleGetApplicationIdByDeltaId(meta_key->id); + break; + case NcmContentMetaType_DataPatch: + app_id = titleGetApplicationIdByDataPatchId(meta_key->id); + break; + default: + break; + } + + return app_id; +} + static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_storage) { if (!title_storage || title_storage->storage_id < NcmStorageId_GameCard || title_storage->storage_id > NcmStorageId_SdCard || !serviceIsActive(&(title_storage->ncm_db.s))) @@ -1850,12 +1879,7 @@ static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_sto utilsGenerateFormattedSizeString((double)cur_title_info->size, cur_title_info->size_str, sizeof(cur_title_info->size_str)); /* Retrieve application metadata. */ - u64 app_id = (cur_title_info->meta_key.type <= NcmContentMetaType_Application ? cur_title_info->meta_key.id : \ - (cur_title_info->meta_key.type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(cur_title_info->meta_key.id) : \ - (cur_title_info->meta_key.type == NcmContentMetaType_AddOnContent ? titleGetApplicationIdByAddOnContentId(cur_title_info->meta_key.id) : \ - (cur_title_info->meta_key.type == NcmContentMetaType_Delta ? titleGetApplicationIdByDeltaId(cur_title_info->meta_key.id) : \ - (cur_title_info->meta_key.type == NcmContentMetaType_DataPatch ? titleGetApplicationIdByDataPatchId(cur_title_info->meta_key.id) : 0))))); - + u64 app_id = titleGetApplicationIdByMetaKey(&(cur_title_info->meta_key)); cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, storage_id == NcmStorageId_BuiltInSystem, 0); if (!cur_title_info->app_metadata && storage_id == NcmStorageId_BuiltInSystem) { @@ -2097,10 +2121,7 @@ static void titleUpdateTitleInfoLinkedLists(void) { /* We're dealing with a patch, an add-on content or an add-on content patch. */ /* We'll just retrieve a pointer to the first matching user application entry and use it to set a pointer to an application metadata entry. */ - u64 app_id = (child_info->meta_key.type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(child_info->meta_key.id) : \ - (child_info->meta_key.type == NcmContentMetaType_AddOnContent ? titleGetApplicationIdByAddOnContentId(child_info->meta_key.id) : \ - titleGetApplicationIdByDataPatchId(child_info->meta_key.id))); - + u64 app_id = titleGetApplicationIdByMetaKey(&(child_info->meta_key)); TitleInfo *parent = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id); if (parent) { @@ -2263,14 +2284,9 @@ static bool titleRefreshGameCardTitleInfo(void) TitleInfo *cur_title_info = titles[i]; if (!cur_title_info) continue; - u64 app_id = (cur_title_info->meta_key.type <= NcmContentMetaType_Application ? cur_title_info->meta_key.id : \ - (cur_title_info->meta_key.type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(cur_title_info->meta_key.id) : \ - (cur_title_info->meta_key.type == NcmContentMetaType_AddOnContent ? titleGetApplicationIdByAddOnContentId(cur_title_info->meta_key.id) : \ - (cur_title_info->meta_key.type == NcmContentMetaType_Delta ? titleGetApplicationIdByDeltaId(cur_title_info->meta_key.id) : \ - (cur_title_info->meta_key.type == NcmContentMetaType_DataPatch ? titleGetApplicationIdByDataPatchId(cur_title_info->meta_key.id) : 0))))); - - /* Do not proceed if we couldn't retrieve an application ID, if application metadata has already been retrieved, or if we can successfully retrieve it. */ - if (!app_id || cur_title_info->app_metadata != NULL || (cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, false, extra_app_count)) != NULL) continue; + /* Do not proceed if application metadata has already been retrieved, or if we can successfully retrieve it. */ + u64 app_id = titleGetApplicationIdByMetaKey(&(cur_title_info->meta_key)); + if (cur_title_info->app_metadata != NULL || (cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, false, extra_app_count)) != NULL) continue; /* Retrieve application metadata pointer. */ TitleApplicationMetadata *cur_app_metadata = g_userMetadata[g_userMetadataCount + extra_app_count]; @@ -2568,7 +2584,7 @@ static int titleInfoEntrySortFunction(const void *a, const void *b) static char *titleGetPatchVersionString(TitleInfo *title_info) { NcmContentInfo *nacp_content = NULL; - u8 storage_id = 0, hfs_partition_type = 0; + u8 storage_id = NcmStorageId_None, hfs_partition_type = HashFileSystemPartitionType_None; NcaContext *nca_ctx = NULL; NacpContext nacp_ctx = {0}; char display_version[0x11] = {0}, *str = NULL; @@ -2581,7 +2597,7 @@ static char *titleGetPatchVersionString(TitleInfo *title_info) /* Update parameters. */ storage_id = title_info->storage_id; - if (storage_id == NcmStorageId_GameCard) hfs_partition_type = GameCardHashFileSystemPartitionType_Secure; + if (storage_id == NcmStorageId_GameCard) hfs_partition_type = HashFileSystemPartitionType_Secure; /* Allocate memory for the NCA context. */ nca_ctx = calloc(1, sizeof(NcaContext)); diff --git a/source/core/usb.c b/source/core/usb.c index 9df22d9..ae033f1 100644 --- a/source/core/usb.c +++ b/source/core/usb.c @@ -25,7 +25,9 @@ #include "nxdt_utils.h" #include "usb.h" -#define USB_ABI_VERSION 1 +#define USB_ABI_VERSION_MAJOR 1 +#define USB_ABI_VERSION_MINOR 1 +#define USB_ABI_VERSION ((USB_ABI_VERSION_MAJOR << 4) | USB_ABI_VERSION_MINOR) #define USB_CMD_HEADER_MAGIC 0x4E584454 /* "NXDT". */ @@ -41,7 +43,7 @@ #define USB_FS_EP_MAX_PACKET_SIZE 0x40 /* 64 bytes. */ #define USB_HS_BCD_REVISION 0x0200 /* USB 2.0. */ -#define USB_HS_EP0_MAX_PACKET_SIZE USB_FS_EP0_MAX_PACKET_SIZE /* 64 bytes. */ +#define USB_HS_EP0_MAX_PACKET_SIZE 0x40 /* 64 bytes. */ #define USB_HS_EP_MAX_PACKET_SIZE 0x200 /* 512 bytes. */ #define USB_SS_BCD_REVISION 0x0300 /* USB 3.0. */ @@ -145,27 +147,27 @@ enum usb_supported_speed { }; /// Imported from libusb, with some adjustments. -struct usb_bos_descriptor { +struct PACKED usb_bos_descriptor { u8 bLength; u8 bDescriptorType; ///< Must match USB_DT_BOS. u16 wTotalLength; ///< Length of this descriptor and all of its sub descriptors. u8 bNumDeviceCaps; ///< The number of separate device capability descriptors in the BOS. -} PACKED; +}; NXDT_ASSERT(struct usb_bos_descriptor, 0x5); /// Imported from libusb, with some adjustments. -struct usb_2_0_extension_descriptor { +struct PACKED usb_2_0_extension_descriptor { u8 bLength; u8 bDescriptorType; ///< Must match USB_DT_DEVICE_CAPABILITY. u8 bDevCapabilityType; ///< Must match USB_BT_USB_2_0_EXTENSION. u32 bmAttributes; ///< usb_2_0_extension_attributes. -} PACKED; +}; NXDT_ASSERT(struct usb_2_0_extension_descriptor, 0x7); /// Imported from libusb, with some adjustments. -struct usb_ss_usb_device_capability_descriptor { +struct PACKED usb_ss_usb_device_capability_descriptor { u8 bLength; u8 bDescriptorType; ///< Must match USB_DT_DEVICE_CAPABILITY. u8 bDevCapabilityType; ///< Must match USB_BT_SS_USB_DEVICE_CAPABILITY. @@ -174,7 +176,7 @@ struct usb_ss_usb_device_capability_descriptor { u8 bFunctionalitySupport; ///< The lowest speed at which all the functionality that the device supports is available to the user. u8 bU1DevExitLat; ///< U1 Device Exit Latency. u16 bU2DevExitLat; ///< U2 Device Exit Latency. -} PACKED; +}; NXDT_ASSERT(struct usb_ss_usb_device_capability_descriptor, 0xA); @@ -183,7 +185,7 @@ NXDT_ASSERT(struct usb_ss_usb_device_capability_descriptor, 0xA); static Mutex g_usbInterfaceMutex = 0; static UsbDsInterface *g_usbInterface = NULL; static UsbDsEndpoint *g_usbEndpointIn = NULL, *g_usbEndpointOut = NULL; -static bool g_usbInterfaceInit = false; +static bool g_usbInterfaceInit = false, g_usbHos5xEnabled = false; static Event *g_usbStateChangeEvent = NULL; static Thread g_usbDetectionThread = {0}; @@ -214,10 +216,11 @@ NX_INLINE bool usbAllocateTransferBuffer(void); NX_INLINE void usbFreeTransferBuffer(void); static bool usbInitializeComms(void); +static bool usbInitializeComms5x(void); +static bool usbInitializeComms1x(void); static void usbCloseComms(void); -static bool usbInitializeDeviceInterface5x(void); -static bool usbInitializeDeviceInterface1x(void); +static bool _usbSendFileProperties(u64 file_size, const char *filename, u32 nsp_header_size, bool enforce_nsp_mode); NX_INLINE bool usbIsHostAvailable(void); @@ -243,10 +246,10 @@ bool usbInitialize(void) break; } - /* Initialize USB device interface. */ + /* Initialize USB comms. */ if (!usbInitializeComms()) { - LOG_MSG_ERROR("Failed to initialize USB device interface!"); + LOG_MSG_ERROR("Failed to initialize USB comms!"); break; } @@ -334,45 +337,17 @@ u8 usbIsReady(void) return ret; } -bool usbSendFileProperties(u64 file_size, const char *filename, u32 nsp_header_size) +bool usbSendFileProperties(u64 file_size, const char *filename) { bool ret = false; + SCOPED_LOCK(&g_usbInterfaceMutex) ret = _usbSendFileProperties(file_size, filename, 0, false); + return ret; +} - SCOPED_LOCK(&g_usbInterfaceMutex) - { - size_t filename_length = 0; - - if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || !filename || !(filename_length = strlen(filename)) || filename_length >= FS_MAX_PATH || \ - (!g_nspTransferMode && ((file_size && nsp_header_size >= file_size) || g_usbTransferRemainingSize)) || (g_nspTransferMode && nsp_header_size)) - { - LOG_MSG_ERROR("Invalid parameters!"); - goto end; - } - - /* Prepare command data. */ - usbPrepareCommandHeader(UsbCommandType_SendFileProperties, (u32)sizeof(UsbCommandSendFileProperties)); - - UsbCommandSendFileProperties *cmd_block = (UsbCommandSendFileProperties*)(g_usbTransferBuffer + sizeof(UsbCommandHeader)); - memset(cmd_block, 0, sizeof(UsbCommandSendFileProperties)); - - cmd_block->file_size = file_size; - cmd_block->filename_length = (u32)filename_length; - cmd_block->nsp_header_size = nsp_header_size; - snprintf(cmd_block->filename, sizeof(cmd_block->filename), "%s", filename); - - /* Send command. */ - ret = usbSendCommand(); - if (!ret) goto end; - - /* Update variables. */ - g_usbTransferRemainingSize = file_size; - g_usbTransferWrittenSize = 0; - if (!g_nspTransferMode) g_nspTransferMode = (file_size && nsp_header_size); - -end: - if (!ret && g_nspTransferMode) g_nspTransferMode = false; - } - +bool usbSendNspProperties(u64 nsp_size, const char *filename, u32 nsp_header_size) +{ + bool ret = false; + SCOPED_LOCK(&g_usbInterfaceMutex) ret = _usbSendFileProperties(nsp_size, filename, nsp_header_size, true); return ret; } @@ -426,7 +401,7 @@ bool usbSendFileData(void *data, u64 data_size) if (!(ret = usbWrite(buf, data_size))) { LOG_MSG_ERROR("Failed to write 0x%lX bytes long file data chunk from offset 0x%lX! (total size: 0x%lX).", data_size, g_usbTransferWrittenSize, \ - g_usbTransferRemainingSize + g_usbTransferWrittenSize); + g_usbTransferRemainingSize + g_usbTransferWrittenSize); goto end; } @@ -630,7 +605,7 @@ static bool usbStartSession(void) { /* Get the endpoint max packet size from the response sent by the USB host. */ /* This is done to accurately know when and where to enable Zero Length Termination (ZLT) packets during bulk transfers. */ - /* As much as I'd like to avoid this, usb:ds doesn't disclose information such as the exact device descriptor and/or speed used by the USB host. */ + /* As much as I'd like to avoid this, the GetUsbDeviceSpeed cmd from usb:ds is only available in HOS 8.0.0+ -- and we definitely want to provide USB comms under older versions. */ g_usbEndpointMaxPacketSize = ((UsbStatus*)g_usbTransferBuffer)->max_packet_size; if (g_usbEndpointMaxPacketSize != USB_FS_EP_MAX_PACKET_SIZE && g_usbEndpointMaxPacketSize != USB_HS_EP_MAX_PACKET_SIZE && g_usbEndpointMaxPacketSize != USB_SS_EP_MAX_PACKET_SIZE) { @@ -801,9 +776,47 @@ NX_INLINE void usbFreeTransferBuffer(void) static bool usbInitializeComms(void) { Result rc = 0; - bool ret = false, init_dev_if = false; + bool ret = false, is_5x = hosversionAtLeast(5, 0, 0); + + /* Carry out USB comms initialization steps for this HOS version. */ + ret = (is_5x ? usbInitializeComms5x() : usbInitializeComms1x()); + if (!ret) goto end; + + /* Enable USB interface. */ + /* This is always needed regardless of the HOS version. */ + rc = usbDsInterface_EnableInterface(g_usbInterface); + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("usbDsInterface_EnableInterface failed! (0x%X).", rc); + goto end; + } + + /* Additional step needed under HOS 5.0.0+. */ + if (is_5x) + { + rc = usbDsEnable(); + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("usbDsEnable failed! (0x%X).", rc); + goto end; + } + + g_usbHos5xEnabled = true; + } + + ret = true; + +end: + if (!ret) usbCloseComms(); + + return ret; +} + +static bool usbInitializeComms5x(void) +{ + Result rc = 0; + bool ret = false; - /* Used on HOS >= 5.0.0. */ struct usb_device_descriptor device_descriptor = { .bLength = USB_DT_DEVICE_SIZE, .bDescriptorType = USB_DT_DEVICE, @@ -849,130 +862,6 @@ static bool usbInitializeComms(void) usb3_devcap_desc->bU1DevExitLat = 0; usb3_devcap_desc->bU2DevExitLat = 0; - /* Used on HOS < 5.0.0. */ - static const UsbDsDeviceInfo device_info = { - .idVendor = USB_DEV_VID, - .idProduct = USB_DEV_PID, - .bcdDevice = USB_DEV_BCD_REL, - .Manufacturer = APP_AUTHOR, - .Product = APP_TITLE, - .SerialNumber = APP_VERSION - }; - - rc = usbDsInitialize(); - if (R_FAILED(rc)) - { - LOG_MSG_ERROR("usbDsInitialize failed! (0x%X).", rc); - goto end; - } - - if (hosversionAtLeast(5, 0, 0)) - { - /* Set language string descriptor. */ - rc = usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, num_supported_langs); - if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsAddUsbLanguageStringDescriptor failed! (0x%X).", rc); - - /* Set manufacturer string descriptor. */ - if (R_SUCCEEDED(rc)) - { - rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iManufacturer), APP_AUTHOR); - if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsAddUsbStringDescriptor failed! (0x%X) (manufacturer).", rc); - } - - /* Set product string descriptor. */ - if (R_SUCCEEDED(rc)) - { - rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iProduct), APP_TITLE); - if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsAddUsbStringDescriptor failed! (0x%X) (product).", rc); - } - - /* Set serial number string descriptor. */ - if (R_SUCCEEDED(rc)) - { - rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iSerialNumber), APP_VERSION); - if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsAddUsbStringDescriptor failed! (0x%X) (serial number).", rc); - } - - /* Set device descriptors. */ - - if (R_SUCCEEDED(rc)) - { - rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Full, &device_descriptor); /* Full Speed is USB 1.1. */ - if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsSetUsbDeviceDescriptor failed! (0x%X) (USB 1.1).", rc); - } - - if (R_SUCCEEDED(rc)) - { - /* Update USB revision before proceeding. */ - device_descriptor.bcdUSB = USB_HS_BCD_REVISION; - - rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, &device_descriptor); /* High Speed is USB 2.0. */ - if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsSetUsbDeviceDescriptor failed! (0x%X) (USB 2.0).", rc); - } - - if (R_SUCCEEDED(rc)) - { - /* Update USB revision and upgrade control endpoint packet size before proceeding. */ - device_descriptor.bcdUSB = USB_SS_BCD_REVISION; - device_descriptor.bMaxPacketSize0 = USB_SS_EP0_MAX_PACKET_SIZE; - - rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, &device_descriptor); /* Super Speed is USB 3.0. */ - if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsSetUsbDeviceDescriptor failed! (0x%X) (USB 3.0).", rc); - } - - /* Set Binary Object Store. */ - if (R_SUCCEEDED(rc)) - { - rc = usbDsSetBinaryObjectStore(bos, USB_BOS_SIZE); - if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsSetBinaryObjectStore failed! (0x%X).", rc); - } - } else { - /* Set VID, PID and BCD. */ - rc = usbDsSetVidPidBcd(&device_info); - if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsSetVidPidBcd failed! (0x%X).", rc); - } - - if (R_FAILED(rc)) goto end; - - /* Initialize USB device interface. */ - init_dev_if = (hosversionAtLeast(5, 0, 0) ? usbInitializeDeviceInterface5x() : usbInitializeDeviceInterface1x()); - if (!init_dev_if) - { - LOG_MSG_ERROR("Failed to initialize USB device interface!"); - goto end; - } - - if (hosversionAtLeast(5, 0, 0)) - { - rc = usbDsEnable(); - if (R_FAILED(rc)) - { - LOG_MSG_ERROR("usbDsEnable failed! (0x%X).", rc); - goto end; - } - } - - ret = true; - -end: - if (!ret) usbCloseComms(); - - return ret; -} - -static void usbCloseComms(void) -{ - usbDsExit(); - - g_usbInterface = NULL; - g_usbEndpointIn = NULL; - g_usbEndpointOut = NULL; -} - -static bool usbInitializeDeviceInterface5x(void) -{ - Result rc = 0; - struct usb_interface_descriptor interface_descriptor = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, @@ -1011,12 +900,77 @@ static bool usbInitializeDeviceInterface5x(void) .wBytesPerInterval = 0 }; + /* Set language string descriptor. */ + rc = usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, num_supported_langs); + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("usbDsAddUsbLanguageStringDescriptor failed! (0x%X).", rc); + goto end; + } + + /* Set manufacturer string descriptor. */ + rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iManufacturer), APP_AUTHOR); + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("usbDsAddUsbStringDescriptor failed! (0x%X) (manufacturer).", rc); + goto end; + } + + /* Set product string descriptor. */ + rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iProduct), APP_TITLE); + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("usbDsAddUsbStringDescriptor failed! (0x%X) (product).", rc); + goto end; + } + + /* Set serial number string descriptor. */ + rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iSerialNumber), APP_VERSION); + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("usbDsAddUsbStringDescriptor failed! (0x%X) (serial number).", rc); + goto end; + } + + /* Set device descriptors. */ + rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Full, &device_descriptor); /* Full Speed is USB 1.1. */ + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("usbDsSetUsbDeviceDescriptor failed! (0x%X) (USB 1.1).", rc); + goto end; + } + + device_descriptor.bcdUSB = USB_HS_BCD_REVISION; + rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, &device_descriptor); /* High Speed is USB 2.0. */ + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("usbDsSetUsbDeviceDescriptor failed! (0x%X) (USB 2.0).", rc); + goto end; + } + + device_descriptor.bcdUSB = USB_SS_BCD_REVISION; + device_descriptor.bMaxPacketSize0 = USB_SS_EP0_MAX_PACKET_SIZE; + rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, &device_descriptor); /* Super Speed is USB 3.0. */ + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("usbDsSetUsbDeviceDescriptor failed! (0x%X) (USB 3.0).", rc); + goto end; + } + + /* Set Binary Object Store. */ + rc = usbDsSetBinaryObjectStore(bos, USB_BOS_SIZE); + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("usbDsSetBinaryObjectStore failed! (0x%X).", rc); + goto end; + } + /* Setup interface. */ rc = usbDsRegisterInterface(&g_usbInterface); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsRegisterInterface failed! (0x%X).", rc); - return false; + goto end; } interface_descriptor.bInterfaceNumber = g_usbInterface->interface_index; @@ -1028,85 +982,83 @@ static bool usbInitializeDeviceInterface5x(void) if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 1.1) (interface).", rc); - return false; + goto end; } rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Full, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 1.1) (in endpoint).", rc); - return false; + goto end; } rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Full, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 1.1) (out endpoint).", rc); - return false; + goto end; } /* High Speed config (USB 2.0). */ - endpoint_descriptor_in.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE; - endpoint_descriptor_out.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE; + endpoint_descriptor_in.wMaxPacketSize = endpoint_descriptor_out.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE; rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_High, &interface_descriptor, USB_DT_INTERFACE_SIZE); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 2.0) (interface).", rc); - return false; + goto end; } rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_High, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 2.0) (in endpoint).", rc); - return false; + goto end; } rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_High, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 2.0) (out endpoint).", rc); - return false; + goto end; } /* Super Speed config (USB 3.0). */ - endpoint_descriptor_in.wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE; - endpoint_descriptor_out.wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE; + endpoint_descriptor_in.wMaxPacketSize = endpoint_descriptor_out.wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE; rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &interface_descriptor, USB_DT_INTERFACE_SIZE); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 3.0) (interface).", rc); - return false; + goto end; } rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 3.0) (in endpoint).", rc); - return false; + goto end; } rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 3.0) (in endpoint companion).", rc); - return false; + goto end; } rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 3.0) (out endpoint).", rc); - return false; + goto end; } rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 3.0) (out endpoint companion).", rc); - return false; + goto end; } /* Setup endpoints. */ @@ -1114,29 +1066,35 @@ static bool usbInitializeDeviceInterface5x(void) if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_RegisterEndpoint failed! (0x%X) (in endpoint).", rc); - return false; + goto end; } rc = usbDsInterface_RegisterEndpoint(g_usbInterface, &g_usbEndpointOut, endpoint_descriptor_out.bEndpointAddress); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_RegisterEndpoint failed! (0x%X) (out endpoint).", rc); - return false; + goto end; } - rc = usbDsInterface_EnableInterface(g_usbInterface); - if (R_FAILED(rc)) - { - LOG_MSG_ERROR("usbDsInterface_EnableInterface failed! (0x%X).", rc); - return false; - } + ret = true; - return true; +end: + return ret; } -static bool usbInitializeDeviceInterface1x(void) +static bool usbInitializeComms1x(void) { Result rc = 0; + bool ret = false; + + static const UsbDsDeviceInfo device_info = { + .idVendor = USB_DEV_VID, + .idProduct = USB_DEV_PID, + .bcdDevice = USB_DEV_BCD_REL, + .Manufacturer = APP_AUTHOR, + .Product = APP_TITLE, + .SerialNumber = APP_VERSION + }; struct usb_interface_descriptor interface_descriptor = { .bLength = USB_DT_INTERFACE_SIZE, @@ -1167,12 +1125,20 @@ static bool usbInitializeDeviceInterface1x(void) .bInterval = 0 }; + /* Set VID, PID and BCD. */ + rc = usbDsSetVidPidBcd(&device_info); + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("usbDsSetVidPidBcd failed! (0x%X).", rc); + goto end; + } + /* Setup interface. */ rc = usbDsGetDsInterface(&g_usbInterface, &interface_descriptor, "usb"); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsGetDsInterface failed! (0x%X).", rc); - return false; + goto end; } /* Setup endpoints. */ @@ -1180,24 +1146,94 @@ static bool usbInitializeDeviceInterface1x(void) if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_GetDsEndpoint failed! (0x%X) (in endpoint).", rc); - return false; + goto end; } rc = usbDsInterface_GetDsEndpoint(g_usbInterface, &g_usbEndpointOut, &endpoint_descriptor_out); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsInterface_GetDsEndpoint failed! (0x%X) (out endpoint).", rc); - return false; + goto end; } - rc = usbDsInterface_EnableInterface(g_usbInterface); - if (R_FAILED(rc)) + ret = true; + +end: + return ret; +} + +static void usbCloseComms(void) +{ + bool is_5x = hosversionAtLeast(5, 0, 0); + + if (is_5x && g_usbHos5xEnabled) { - LOG_MSG_ERROR("usbDsInterface_EnableInterface failed! (0x%X).", rc); + usbDsDisable(); + g_usbHos5xEnabled = false; + } + + if (g_usbEndpointOut) + { + usbDsEndpoint_Close(g_usbEndpointOut); + g_usbEndpointOut = NULL; + } + + if (g_usbEndpointIn) + { + usbDsEndpoint_Close(g_usbEndpointIn); + g_usbEndpointIn = NULL; + } + + if (g_usbInterface) + { + /* usbDsInterface_DisableInterface() is internally called here. */ + usbDsInterface_Close(g_usbInterface); + g_usbInterface = NULL; + } + + if (is_5x) usbDsClearDeviceData(); +} + +static bool _usbSendFileProperties(u64 file_size, const char *filename, u32 nsp_header_size, bool enforce_nsp_mode) +{ + bool ret = false; + size_t filename_length = 0; + + /* Disallow sending new files if we're not in NSP transfer mode and the remaining transfer size isn't zero. */ + /* Allow empty files if we're not in NSP transfer mode. */ + /* Disallow sending new NSPs if we're already in NSP transfer mode. */ + if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || (!g_nspTransferMode && g_usbTransferRemainingSize) || \ + !filename || !(filename_length = strlen(filename)) || filename_length >= FS_MAX_PATH || (!enforce_nsp_mode && nsp_header_size) || \ + (enforce_nsp_mode && (g_nspTransferMode || !file_size || !nsp_header_size || nsp_header_size >= file_size))) + { + LOG_MSG_ERROR("Invalid parameters!"); return false; } - return true; + /* Prepare command data. */ + usbPrepareCommandHeader(UsbCommandType_SendFileProperties, (u32)sizeof(UsbCommandSendFileProperties)); + + UsbCommandSendFileProperties *cmd_block = (UsbCommandSendFileProperties*)(g_usbTransferBuffer + sizeof(UsbCommandHeader)); + memset(cmd_block, 0, sizeof(UsbCommandSendFileProperties)); + + cmd_block->file_size = file_size; + cmd_block->filename_length = (u32)filename_length; + cmd_block->nsp_header_size = nsp_header_size; + snprintf(cmd_block->filename, sizeof(cmd_block->filename), "%s", filename); + + /* Send command. */ + ret = usbSendCommand(); + if (ret) + { + g_usbTransferRemainingSize = file_size; + g_usbTransferWrittenSize = 0; + if (!g_nspTransferMode && enforce_nsp_mode) g_nspTransferMode = true; + } else { + g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0; + g_nspTransferMode = false; + } + + return ret; } NX_INLINE bool usbIsHostAvailable(void) diff --git a/source/fatfs/ff.c b/source/fatfs/ff.c index 24f7a15..6709fd1 100644 --- a/source/fatfs/ff.c +++ b/source/fatfs/ff.c @@ -945,7 +945,7 @@ static void unlock_volume ( /*-----------------------------------------------------------------------*/ static FRESULT chk_share ( /* Check if the file can be accessed */ - DIR* dp, /* Directory object pointing the file to be checked */ + FDIR* dp, /* Directory object pointing the file to be checked */ int acc /* Desired access type (0:Read mode open, 1:Write mode open, 2:Delete or rename) */ ) { @@ -981,7 +981,7 @@ static int enq_share (void) /* Check if an entry is available for a new object * static UINT inc_share ( /* Increment object open counter and returns its index (0:Internal error) */ - DIR* dp, /* Directory object pointing the file to register or increment */ + FDIR* dp, /* Directory object pointing the file to register or increment */ int acc /* Desired access (0:Read, 1:Write, 2:Delete/Rename) */ ) { @@ -1697,7 +1697,7 @@ static FRESULT dir_clear ( /* Returns FR_OK or FR_DISK_ERR */ /*-----------------------------------------------------------------------*/ static FRESULT dir_sdi ( /* FR_OK(0):succeeded, !=0:error */ - DIR* dp, /* Pointer to directory object */ + FDIR* dp, /* Pointer to directory object */ DWORD ofs /* Offset of directory table */ ) { @@ -1745,7 +1745,7 @@ static FRESULT dir_sdi ( /* FR_OK(0):succeeded, !=0:error */ /*-----------------------------------------------------------------------*/ static FRESULT dir_next ( /* FR_OK(0):succeeded, FR_NO_FILE:End of table, FR_DENIED:Could not stretch */ - DIR* dp, /* Pointer to the directory object */ + FDIR* dp, /* Pointer to the directory object */ int stretch /* 0: Do not stretch table, 1: Stretch table if needed */ ) { @@ -1806,7 +1806,7 @@ static FRESULT dir_next ( /* FR_OK(0):succeeded, FR_NO_FILE:End of table, FR_DEN /*-----------------------------------------------------------------------*/ static FRESULT dir_alloc ( /* FR_OK(0):succeeded, !=0:error */ - DIR* dp, /* Pointer to the directory object */ + FDIR* dp, /* Pointer to the directory object */ UINT n_ent /* Number of contiguous entries to allocate */ ) { @@ -2130,7 +2130,7 @@ static DWORD xsum32 ( /* Returns 32-bit checksum */ /*------------------------------------*/ static FRESULT load_xdir ( /* FR_INT_ERR: invalid entry block */ - DIR* dp /* Reading directory object pointing top of the entry block to load */ + FDIR* dp /* Reading directory object pointing top of the entry block to load */ ) { FRESULT res; @@ -2199,7 +2199,7 @@ static void init_alloc_info ( /*------------------------------------------------*/ static FRESULT load_obj_xdir ( - DIR* dp, /* Blank directory object to be used to access containing directory */ + FDIR* dp, /* Blank directory object to be used to access containing directory */ const FFOBJID* obj /* Object with its containing directory information */ ) { @@ -2228,7 +2228,7 @@ static FRESULT load_obj_xdir ( /*----------------------------------------*/ static FRESULT store_xdir ( - DIR* dp /* Pointer to the directory object */ + FDIR* dp /* Pointer to the directory object */ ) { FRESULT res; @@ -2306,7 +2306,7 @@ static void create_xdir ( #define DIR_READ_LABEL(dp) dir_read(dp, 1) static FRESULT dir_read ( - DIR* dp, /* Pointer to the directory object */ + FDIR* dp, /* Pointer to the directory object */ int vol /* Filtered by 0:file/directory or 1:volume label */ ) { @@ -2384,7 +2384,7 @@ static FRESULT dir_read ( /*-----------------------------------------------------------------------*/ static FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */ - DIR* dp /* Pointer to the directory object with the file name */ + FDIR* dp /* Pointer to the directory object with the file name */ ) { FRESULT res; @@ -2465,7 +2465,7 @@ static FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */ /*-----------------------------------------------------------------------*/ static FRESULT dir_register ( /* FR_OK:succeeded, FR_DENIED:no free entry or too many SFN collision, FR_DISK_ERR:disk error */ - DIR* dp /* Target directory with object name to be created */ + FDIR* dp /* Target directory with object name to be created */ ) { FRESULT res; @@ -2492,7 +2492,7 @@ static FRESULT dir_register ( /* FR_OK:succeeded, FR_DENIED:no free entry or too res = fill_last_frag(&dp->obj, dp->clust, 0xFFFFFFFF); /* Fill the last fragment on the FAT if needed */ if (res != FR_OK) return res; if (dp->obj.sclust != 0) { /* Is it a sub-directory? */ - DIR dj; + FDIR dj; res = load_obj_xdir(&dj, &dp->obj); /* Load the object status */ if (res != FR_OK) return res; @@ -2571,7 +2571,7 @@ static FRESULT dir_register ( /* FR_OK:succeeded, FR_DENIED:no free entry or too /*-----------------------------------------------------------------------*/ static FRESULT dir_remove ( /* FR_OK:Succeeded, FR_DISK_ERR:A disk error */ - DIR* dp /* Directory object pointing the entry to be removed */ + FDIR* dp /* Directory object pointing the entry to be removed */ ) { FRESULT res; @@ -2617,7 +2617,7 @@ static FRESULT dir_remove ( /* FR_OK:Succeeded, FR_DISK_ERR:A disk error */ /*-----------------------------------------------------------------------*/ static void get_fileinfo ( - DIR* dp, /* Pointer to the directory object */ + FDIR* dp, /* Pointer to the directory object */ FILINFO* fno /* Pointer to the file information to be filled */ ) { @@ -2847,7 +2847,7 @@ static int pattern_match ( /* 0:mismatched, 1:matched */ /*-----------------------------------------------------------------------*/ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not create */ - DIR* dp, /* Pointer to the directory object */ + FDIR* dp, /* Pointer to the directory object */ const TCHAR** path /* Pointer to pointer to the segment in the path string */ ) { @@ -3051,7 +3051,7 @@ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not cr /*-----------------------------------------------------------------------*/ static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ - DIR* dp, /* Directory object to return last directory and found object */ + FDIR* dp, /* Directory object to return last directory and found object */ const TCHAR* path /* Full-path string to find a file or directory */ ) { @@ -3073,7 +3073,7 @@ static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ dp->obj.n_frag = 0; /* Invalidate last fragment counter of the object */ #if FF_FS_RPATH != 0 if (fs->fs_type == FS_EXFAT && dp->obj.sclust) { /* exFAT: Retrieve the sub-directory's status */ - DIR dj; + FDIR dj; dp->obj.c_scl = fs->cdc_scl; dp->obj.c_size = fs->cdc_size; @@ -3534,7 +3534,7 @@ static FRESULT mount_volume ( /* FR_OK(0): successful, !=0: an error occurred */ if (nrsv == 0) return FR_NO_FILESYSTEM; /* (Must not be 0) */ /* Determine the FAT sub type */ - sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZDIRE); /* RSV + FAT + DIR */ + sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZDIRE); /* RSV + FAT + FDIR */ if (tsect < sysect) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ nclst = (tsect - sysect) / fs->csize; /* Number of clusters */ if (nclst == 0) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ @@ -3613,7 +3613,7 @@ static FRESULT mount_volume ( /* FR_OK(0): successful, !=0: an error occurred */ /*-----------------------------------------------------------------------*/ static FRESULT validate ( /* Returns FR_OK or FR_INVALID_OBJECT */ - FFOBJID* obj, /* Pointer to the FFOBJID, the 1st member in the FIL/DIR structure, to check validity */ + FFOBJID* obj, /* Pointer to the FFOBJID, the 1st member in the FIL/FDIR structure, to check validity */ FATFS** rfs /* Pointer to pointer to the owner filesystem object to return */ ) { @@ -3723,7 +3723,7 @@ FRESULT f_open ( ) { FRESULT res; - DIR dj; + FDIR dj; FATFS *fs; #if !FF_FS_READONLY DWORD cl, bcs, clst, tm; @@ -3766,7 +3766,7 @@ FRESULT f_open ( mode |= FA_CREATE_ALWAYS; /* File is created */ } else { /* Any object with the same name is already existing */ - if (dj.obj.attr & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or DIR) */ + if (dj.obj.attr & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or FDIR) */ res = FR_DENIED; } else { if (mode & FA_CREATE_NEW) res = FR_EXIST; /* Cannot create as new file */ @@ -4162,7 +4162,7 @@ FRESULT f_sync ( res = fill_last_frag(&fp->obj, fp->clust, 0xFFFFFFFF); /* Fill last fragment on the FAT if needed */ } if (res == FR_OK) { - DIR dj; + FDIR dj; DEF_NAMBUF INIT_NAMBUF(fs); @@ -4276,7 +4276,7 @@ FRESULT f_chdir ( UINT i; #endif FRESULT res; - DIR dj; + FDIR dj; FATFS *fs; DEF_NAMBUF @@ -4336,7 +4336,7 @@ FRESULT f_getcwd ( ) { FRESULT res; - DIR dj; + FDIR dj; FATFS *fs; UINT i, n; DWORD ccl; @@ -4597,7 +4597,7 @@ FRESULT f_lseek ( /*-----------------------------------------------------------------------*/ FRESULT f_opendir ( - DIR* dp, /* Pointer to directory object to create */ + FDIR* dp, /* Pointer to directory object to create */ const TCHAR* path /* Pointer to the directory path */ ) { @@ -4663,7 +4663,7 @@ FRESULT f_opendir ( /*-----------------------------------------------------------------------*/ FRESULT f_closedir ( - DIR *dp /* Pointer to the directory object to be closed */ + FDIR *dp /* Pointer to the directory object to be closed */ ) { FRESULT res; @@ -4693,7 +4693,7 @@ FRESULT f_closedir ( /*-----------------------------------------------------------------------*/ FRESULT f_readdir ( - DIR* dp, /* Pointer to the open directory object */ + FDIR* dp, /* Pointer to the open directory object */ FILINFO* fno /* Pointer to file information to return */ ) { @@ -4729,7 +4729,7 @@ FRESULT f_readdir ( /*-----------------------------------------------------------------------*/ FRESULT f_findnext ( - DIR* dp, /* Pointer to the open directory object */ + FDIR* dp, /* Pointer to the open directory object */ FILINFO* fno /* Pointer to the file information structure */ ) { @@ -4754,7 +4754,7 @@ FRESULT f_findnext ( /*-----------------------------------------------------------------------*/ FRESULT f_findfirst ( - DIR* dp, /* Pointer to the blank directory object */ + FDIR* dp, /* Pointer to the blank directory object */ FILINFO* fno, /* Pointer to the file information structure */ const TCHAR* path, /* Pointer to the directory to open */ const TCHAR* pattern /* Pointer to the matching pattern */ @@ -4786,7 +4786,7 @@ FRESULT f_stat ( ) { FRESULT res; - DIR dj; + FDIR dj; DEF_NAMBUF @@ -4967,7 +4967,7 @@ FRESULT f_unlink ( { FRESULT res; FATFS *fs; - DIR dj, sdj; + FDIR dj, sdj; DWORD dclst = 0; #if FF_FS_EXFAT FFOBJID obj; @@ -5061,7 +5061,7 @@ FRESULT f_mkdir ( { FRESULT res; FATFS *fs; - DIR dj; + FDIR dj; FFOBJID sobj; DWORD dcl, pcl, tm; DEF_NAMBUF @@ -5146,7 +5146,7 @@ FRESULT f_rename ( { FRESULT res; FATFS *fs; - DIR djo, djn; + FDIR djo, djn; BYTE buf[FF_FS_EXFAT ? SZDIRE * 2 : SZDIRE], *dir; LBA_t sect; DEF_NAMBUF @@ -5193,7 +5193,7 @@ FRESULT f_rename ( #endif { /* At FAT/FAT32 volume */ memcpy(buf, djo.dir, SZDIRE); /* Save directory entry of the object */ - memcpy(&djn, &djo, sizeof (DIR)); /* Duplicate the directory object */ + memcpy(&djn, &djo, sizeof (FDIR)); /* Duplicate the directory object */ res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */ if (res == FR_OK) { /* Is new name already in use by any other object? */ res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST; @@ -5257,7 +5257,7 @@ FRESULT f_chmod ( { FRESULT res; FATFS *fs; - DIR dj; + FDIR dj; DEF_NAMBUF @@ -5303,7 +5303,7 @@ FRESULT f_utime ( { FRESULT res; FATFS *fs; - DIR dj; + FDIR dj; DEF_NAMBUF @@ -5351,7 +5351,7 @@ FRESULT f_getlabel ( { FRESULT res; FATFS *fs; - DIR dj; + FDIR dj; UINT si, di; WCHAR wc; @@ -5450,7 +5450,7 @@ FRESULT f_setlabel ( { FRESULT res; FATFS *fs; - DIR dj; + FDIR dj; BYTE dirvn[22]; UINT di; WCHAR wc; diff --git a/source/gamecard_tab.cpp b/source/gamecard_tab.cpp index c0f4a60..3767808 100644 --- a/source/gamecard_tab.cpp +++ b/source/gamecard_tab.cpp @@ -100,6 +100,7 @@ namespace nxdt::views { TitleApplicationMetadata **app_metadata = NULL; u32 app_metadata_count = 0; + GameCardHeader card_header = {0}; GameCardInfo card_info = {0}; bool update_focused_view = this->IsListItemFocused(); @@ -110,7 +111,7 @@ namespace nxdt::views this->list->invalidate(true); /* Information about how to handle HOS launch errors. */ - /* TODO: remove this after we find a way to fix this issue. */ + /* TODO: remove this if we ever find a way to fix this issue. */ FocusableLabel *launch_error_info = new FocusableLabel(brls::LabelStyle::DESCRIPTION, "gamecard_tab/list/launch_error_info"_i18n, true); launch_error_info->setHorizontalAlign(NVG_ALIGN_CENTER); this->list->addView(launch_error_info); @@ -151,28 +152,70 @@ namespace nxdt::views brls::TableRow *lafw_version = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/lafw_version"_i18n); brls::TableRow *sdk_version = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/sdk_version"_i18n); brls::TableRow *compatibility_type = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/compatibility_type"_i18n); + brls::TableRow *package_id = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/package_id"_i18n); capacity->setValue(this->GetFormattedSizeString(&gamecardGetRomCapacity)); total_size->setValue(this->GetFormattedSizeString(&gamecardGetTotalSize)); trimmed_size->setValue(this->GetFormattedSizeString(&gamecardGetTrimmedSize)); + gamecardGetHeader(&card_header); gamecardGetDecryptedCardInfoArea(&card_info); - const Version *upp_version = &(card_info.upp_version); - update_version->setValue(fmt::format("{}.{}.{}-{}.{} (v{})", upp_version->system_version.major, upp_version->system_version.minor, upp_version->system_version.micro, \ - upp_version->system_version.major_relstep, upp_version->system_version.minor_relstep, upp_version->value)); + const SystemVersion upp_version = card_info.upp_version.system_version; + const SdkAddOnVersion upp_version_old = card_info.upp_version.sdk_addon_version; + + /* TODO: move somewhere else? */ + if (upp_version_old.major == 0 && upp_version_old.minor == 0) + { + std::string upp_version_display = ""; + + switch(upp_version_old.micro) + { + case 0: /* v450 / 0.0.0-450 */ + upp_version_display = "1.0.0"; + break; + case 1: /* v65796 / 0.0.1-260 */ + upp_version_display = "2.0.0"; + break; + case 2: /* v131162 / 0.0.2-90 */ + upp_version_display = "2.1.0"; + break; + case 3: /* v196628 / 0.0.3-20 */ + upp_version_display = "2.2.0"; + break; + case 4: /* v262164 / 0.0.4-20 */ + upp_version_display = "2.3.0"; + break; + default: + break; + } + + if (upp_version_display != "") + { + update_version->setValue(fmt::format("{} ({}.{}.{}-{}) (v{})", upp_version_display, upp_version_old.major, upp_version_old.minor, upp_version_old.micro, \ + upp_version_old.relstep, upp_version_old.value)); + } else { + update_version->setValue(fmt::format("{}.{}.{}-{} (v{})", upp_version_old.major, upp_version_old.minor, upp_version_old.micro, \ + upp_version_old.relstep, upp_version_old.value)); + } + } else { + update_version->setValue(fmt::format("{}.{}.{}-{}.{} (v{})", upp_version.major, upp_version.minor, upp_version.micro, \ + upp_version.major_relstep, upp_version.minor_relstep, upp_version.value)); + } u64 fw_version = card_info.fw_version; lafw_version->setValue(fmt::format("{} ({})", fw_version, fw_version >= GameCardFwVersion_Count ? "generic/unknown"_i18n : gamecardGetRequiredHosVersionString(fw_version))); - const SdkAddOnVersion *fw_mode = &(card_info.fw_mode); - sdk_version->setValue(fmt::format("{}.{}.{}-{} (v{})", fw_mode->major, fw_mode->minor, fw_mode->micro, fw_mode->relstep, fw_mode->value)); + const SdkAddOnVersion fw_mode = card_info.fw_mode.sdk_addon_version; + sdk_version->setValue(fmt::format("{}.{}.{}-{} (v{})", fw_mode.major, fw_mode.minor, fw_mode.micro, fw_mode.relstep, fw_mode.value)); u8 compat_type = card_info.compatibility_type; compatibility_type->setValue(fmt::format("{} ({})", \ compat_type >= GameCardCompatibilityType_Count ? "generic/unknown"_i18n : gamecardGetCompatibilityTypeString(compat_type), \ compat_type)); + package_id->setValue(fmt::format("{:016X}", card_header.package_id)); + this->list->addView(properties_table); /* ListItem elements. */ diff --git a/todo.txt b/todo.txt index 9aa47c5..7a87fe9 100644 --- a/todo.txt +++ b/todo.txt @@ -6,11 +6,11 @@ todo: title: more functions for content lookup? (based on id) title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles - gamecard: functions to display filelist + gamecard: functions to display filelist (devoptab device?) - pfs0: functions to display filelist + pfs0: functions to display filelist (devoptab device?) - romfs: functions to display filelist + romfs: functions to display filelist (devoptab device?) usb: change buffer size? usb: change chunk size?