From dcd1f66790321109c744f37690172f4ffe0ad2b9 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Sat, 11 Nov 2023 21:39:41 +0100 Subject: [PATCH] usb: add extra cmds for extracted FS handling Implements both `StartExtractedFsDump` and `EndExtractedFsDump` commands into both nxdumptool and the Python host script. Other changes include: * poc: wake the write thread up if a preprocessing error occurs in all extracted FS dump functions. Fixes previously unhandled hangups. * poc: verify current NCA hash before sending its last data chunk while dumping a NSP. * host: update command handler to support CancelFileTransfer commands issued in-between SendFileProperties commands. * host: update Markdown document. * usb: use UsbCommandType_Count for the safety check in usbPrepareCommandHeader(). * usb: log both USB command header and command block whenever an error is reported by the host side. --- code_templates/nxdt_rw_poc.c | 77 ++++++++++++++++++++++------- host/README.md | 55 ++++++++++++++++----- host/nxdt_host.py | 83 +++++++++++++++++++++++-------- include/core/usb.h | 7 +++ source/core/usb.c | 95 ++++++++++++++++++++++++++++++------ 5 files changed, 252 insertions(+), 65 deletions(-) diff --git a/code_templates/nxdt_rw_poc.c b/code_templates/nxdt_rw_poc.c index 3198560..d9dd1e8 100644 --- a/code_templates/nxdt_rw_poc.c +++ b/code_templates/nxdt_rw_poc.c @@ -3756,15 +3756,25 @@ static void extractedHfsReadThreadFunc(void *arg) { 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) + if (!shared_thread_data->read_error && shared_thread_data->total_size >= free_space) { consolePrint("dump size exceeds free space\n"); shared_thread_data->read_error = true; - goto end; } + } else { + if (!usbStartExtractedFsDump(shared_thread_data->total_size, filename)) + { + consolePrint("failed to send extracted fs info to host\n"); + shared_thread_data->read_error = true; + } + } + + if (shared_thread_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + goto end; } /* Loop through all file entries. */ @@ -3912,6 +3922,8 @@ static void extractedHfsReadThreadFunc(void *arg) if (shared_thread_data->data_size) condvarWait(&g_readCondvar, &g_fileMutex); mutexUnlock(&g_fileMutex); + if (dev_idx == 1) usbEndExtractedFsDump(); + consolePrint("successfully saved extracted hfs partition data to \"%s\"\n", filename); consoleRefresh(); } @@ -4131,15 +4143,25 @@ static void extractedPartitionFsReadThreadFunc(void *arg) { 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) + if (!shared_thread_data->read_error && shared_thread_data->total_size >= free_space) { consolePrint("dump size exceeds free space\n"); shared_thread_data->read_error = true; - goto end; } + } else { + if (!usbStartExtractedFsDump(shared_thread_data->total_size, filename)) + { + consolePrint("failed to send extracted fs info to host\n"); + shared_thread_data->read_error = true; + } + } + + if (shared_thread_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + goto end; } /* Loop through all file entries. */ @@ -4287,6 +4309,8 @@ static void extractedPartitionFsReadThreadFunc(void *arg) if (shared_thread_data->data_size) condvarWait(&g_readCondvar, &g_fileMutex); mutexUnlock(&g_fileMutex); + if (dev_idx == 1) usbEndExtractedFsDump(); + consolePrint("successfully saved extracted partitionfs section data to \"%s\"\n", filename); consoleRefresh(); } @@ -4437,15 +4461,25 @@ static void extractedRomFsReadThreadFunc(void *arg) { 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) + if (!shared_thread_data->read_error && shared_thread_data->total_size >= free_space) { consolePrint("dump size exceeds free space\n"); shared_thread_data->read_error = true; - goto end; } + } else { + if (!usbStartExtractedFsDump(shared_thread_data->total_size, filename)) + { + consolePrint("failed to send extracted fs info to host\n"); + shared_thread_data->read_error = true; + } + } + + if (shared_thread_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + goto end; } /* Reset current file table offset. */ @@ -4601,6 +4635,8 @@ static void extractedRomFsReadThreadFunc(void *arg) if (shared_thread_data->data_size) condvarWait(&g_readCondvar, &g_fileMutex); mutexUnlock(&g_fileMutex); + if (dev_idx == 1) usbEndExtractedFsDump(); + consolePrint("successfully saved extracted romfs section data to \"%s\"\n", filename); consoleRefresh(); } @@ -5293,6 +5329,19 @@ static void nspThreadFunc(void *arg) // update clean hash calculation sha256ContextUpdate(&clean_sha256_ctx, buf, blksize); + if ((offset + blksize) >= cur_nca_ctx->content_size) + { + // get clean hash + sha256ContextGetHash(&clean_sha256_ctx, clean_sha256_hash); + + // validate clean hash + if (!cnmtVerifyContentHash(&cnmt_ctx, cur_nca_ctx, clean_sha256_hash)) + { + consolePrint("sha256 checksum mismatch for nca \"%s\"\n", cur_nca_ctx->content_id_str); + goto end; + } + } + if (dirty_header) { // write re-encrypted headers @@ -5334,17 +5383,9 @@ static void nspThreadFunc(void *arg) } } - // get hashes - sha256ContextGetHash(&clean_sha256_ctx, clean_sha256_hash); + // get dirty hash sha256ContextGetHash(&dirty_sha256_ctx, dirty_sha256_hash); - // verify content hash - if (!cnmtVerifyContentHash(&cnmt_ctx, cur_nca_ctx, clean_sha256_hash)) - { - consolePrint("sha256 checksum mismatch for nca \"%s\"\n", cur_nca_ctx->content_id_str); - goto end; - } - if (memcmp(clean_sha256_hash, dirty_sha256_hash, SHA256_HASH_SIZE) != 0) { // update content id and hash diff --git a/host/README.md b/host/README.md index 36be4cc..8ee2475 100644 --- a/host/README.md +++ b/host/README.md @@ -21,6 +21,8 @@ Unless stated otherwise, the reader must assume all integer fields in the docume * [CancelFileTransfer](#cancelfiletransfer). * [SendNspHeader](#sendnspheader). * [EndSession](#endsession). + * [StartExtractedFsDump](#startextractedfsdump). + * [EndExtractedFsDump](#endextractedfsdump). * [Status response](#status-response). * [Status codes](#status-codes). * [NSP transfer mode](#nsp-transfer-mode). @@ -101,13 +103,15 @@ Certain commands yield no command block at all, leading to a command block size #### Command IDs -| Value | Name | Description | -|-------|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| -| 0 | [`StartSession`](#startsession) | Starts a USB session between the target console and the USB host device. | -| 1 | [`SendFileProperties`](#sendfileproperties) | Sends file metadata and starts a data transfer process. | -| 2 | [`CancelFileTransfer`](#cancelfiletransfer) | Cancels an ongoing data transfer process started by a previously issued [`SendFileProperties`](#sendfileproperties) command. | -| 3 | [`SendNspHeader`](#sendnspheader) | Sends the `PFS0` header from a Nintendo Submission Package (NSP). Only issued under [NSP transfer mode](#nsp-transfer-mode). | -| 4 | [`EndSession`](#endsession) | Ends a previously stablished USB session between the target console and the USB host device. | +| Value | Name | Description | +|-------|-------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------| +| 0 | [`StartSession`](#startsession) | Starts a USB session between the target console and the USB host device. | +| 1 | [`SendFileProperties`](#sendfileproperties) | Sends file metadata and starts a data transfer process. | +| 2 | [`CancelFileTransfer`](#cancelfiletransfer) | Cancels an ongoing data transfer process started by a previously issued [`SendFileProperties`](#sendfileproperties) command. | +| 3 | [`SendNspHeader`](#sendnspheader) | Sends the `PFS0` header from a Nintendo Submission Package (NSP). Only issued under [NSP transfer mode](#nsp-transfer-mode). | +| 4 | [`EndSession`](#endsession) | Ends a previously stablished USB session between the target console and the USB host device. | +| 5 | [`StartExtractedFsDump`](#startextractedfsdump) | Informs the host device that an extracted filesystem dump (e.g. HFS, PFS, RomFS) is about to begin. | +| 6 | [`EndExtractedFsDump`](#endextractedfsdump) | Informs the host device that a previously started filesystem dump (via [`StartExtractedFsDump`](#startextractedfsdump)) has finished. | ### Command blocks @@ -134,11 +138,11 @@ Size: 0x320 bytes. | Offset | Size | Type | Description | |--------|-------|---------------|----------------------------------------------| -| 0x000 | 0x08 | `uint64_t` | File size. | -| 0x008 | 0x04 | `uint32_t` | Path length. | -| 0x00C | 0x04 | `uint32_t` | [NSP header size](#nsp-transfer-mode). | +| 0x000 | 0x008 | `uint64_t` | File size. | +| 0x008 | 0x004 | `uint32_t` | Path length. | +| 0x00C | 0x004 | `uint32_t` | [NSP header size](#nsp-transfer-mode). | | 0x010 | 0x301 | `char[769]` | UTF-8 encoded path (NULL-terminated string). | -| 0x311 | 0x0F | `uint8_t[15]` | Reserved. | +| 0x311 | 0x00F | `uint8_t[15]` | Reserved. | Sent right before starting a file transfer. If it succeeds, a data transfer stage will take place using 8 MiB (0x800000) chunks. If needed, the last chunk will be truncated. @@ -158,7 +162,12 @@ Finally, it should be noted that it's possible for the `filesize` field to be ze Yields no command block. Expects a status response, just like the rest of the commands. -This command can only be issued during the file data transfer stage from a [SendFileProperties](#sendfileproperties) command. It is used to gracefully cancel an ongoing file transfer while also keeping the USB session alive. It's up to the USB host to decide what to do with the incomplete data. +This command can only be issued under two different scenarios: + +* During the file data transfer stage from a [SendFileProperties](#sendfileproperties) command. +* In-between two different [SendFileProperties](#sendfileproperties) commands while under [NSP transfer mode](#nsp-transfer-mode). + +It is used to gracefully cancel an ongoing file transfer while also keeping the USB session alive. It's up to the USB host to decide what to do with the incomplete data. The easiest way to detect this command during a file transfer is by checking the length of the last received block and then parse it to see if it matches a `CancelFileTransfer` command header. @@ -176,6 +185,28 @@ Yields no command block. Expects a status response, just like the rest of the co This command is only issued while exiting nxdumptool, as long as the target console is connected to a host device and a USB session has been successfully established. +#### StartExtractedFsDump + +Size: 0x310 bytes. + +| Offset | Size | Type | Description | +|--------|-------|---------------|----------------------------------------------------------------| +| 0x000 | 0x008 | `uint64_t` | Extracted FS dump size. | +| 0x008 | 0x301 | `char[769]` | UTF-8 encoded extracted FS root path (NULL-terminated string). | +| 0x309 | 0x006 | `uint8_t[6]` | Reserved. | + +Sent right before dumping a Switch FS in extracted form (e.g. HFS, PFS, RomFS) using multiple [SendFileProperties](#sendfileproperties) commands in succession. + +The extracted FS dump size field can be used by the host device to calculate an ETA for the overall FS dump. + +The extracted FS root path represents a path relative to the output directory where all the extracted FS entries are stored. All file paths from the extracted FS dump will begin with this string. + +#### EndExtractedFsDump + +Yields no command block. Expects a status response, just like the rest of the commands. + +This command is only issued after all file entries from an extracted FS dump (started via [`StartExtractedFsDump`](#startextractedfsdump)) have been successfully transferred to the host device. + ### Status response Size: 0x10 bytes. diff --git a/host/nxdt_host.py b/host/nxdt_host.py index bad4929..0b466b4 100644 --- a/host/nxdt_host.py +++ b/host/nxdt_host.py @@ -83,7 +83,7 @@ USB_DEV_MANUFACTURER = 'DarkMatterCore' USB_DEV_PRODUCT = 'nxdumptool' # USB timeout (milliseconds). -USB_TRANSFER_TIMEOUT = 5000 +USB_TRANSFER_TIMEOUT = 10000 # USB transfer block size. USB_TRANSFER_BLOCK_SIZE = 0x800000 @@ -102,15 +102,18 @@ USB_ABI_VERSION_MINOR = 1 USB_CMD_HEADER_SIZE = 0x10 # USB command IDs. -USB_CMD_START_SESSION = 0 -USB_CMD_SEND_FILE_PROPERTIES = 1 -USB_CMD_CANCEL_FILE_TRANSFER = 2 -USB_CMD_SEND_NSP_HEADER = 3 -USB_CMD_END_SESSION = 4 +USB_CMD_START_SESSION = 0 +USB_CMD_SEND_FILE_PROPERTIES = 1 +USB_CMD_CANCEL_FILE_TRANSFER = 2 +USB_CMD_SEND_NSP_HEADER = 3 +USB_CMD_END_SESSION = 4 +USB_CMD_START_EXTRACTED_FS_DUMP = 5 +USB_CMD_END_EXTRACTED_FS_DUMP = 6 # USB command block sizes. -USB_CMD_BLOCK_SIZE_START_SESSION = 0x10 -USB_CMD_BLOCK_SIZE_SEND_FILE_PROPERTIES = 0x320 +USB_CMD_BLOCK_SIZE_START_SESSION = 0x10 +USB_CMD_BLOCK_SIZE_SEND_FILE_PROPERTIES = 0x320 +USB_CMD_BLOCK_SIZE_START_EXTRACTED_FS_DUMP = 0x310 # Max filename length (file properties). USB_FILE_PROPERTIES_MAX_NAME_LENGTH = 0x300 @@ -567,9 +570,14 @@ def utilsGetPath(path_arg: str, fallback_path: str, is_file: bool, create: bool def utilsIsValueAlignedToEndpointPacketSize(value: int) -> bool: return bool((value & (g_usbEpMaxPacketSize - 1)) == 0) -def utilsResetNspInfo() -> None: +def utilsResetNspInfo(delete: bool = False) -> None: global g_nspTransferMode, g_nspSize, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath + if g_nspFile: + g_nspFile.close() + if delete: + os.remove(g_nspFilePath) + # Reset NSP transfer mode info. g_nspTransferMode = False g_nspSize = 0 @@ -868,9 +876,7 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None: def cancelTransfer(): # Cancel file transfer. - file.close() - os.remove(fullpath) - utilsResetNspInfo() + utilsResetNspInfo(True) if use_pbar: g_progressBarWindow.end() @@ -941,6 +947,19 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None: return USB_STATUS_SUCCESS +def usbHandleCancelFileTransfer(cmd_block: bytes) -> int: + #assert g_logger is not None + + g_logger.debug(f'Received CancelFileTransfer ({USB_CMD_START_SESSION:02X}) command.') + + if g_nspTransferMode: + utilsResetNspInfo(True) + g_logger.warning('Transfer cancelled.') + return USB_STATUS_SUCCESS + else: + g_logger.error('Unexpected transfer cancellation.') + return USB_STATUS_MALFORMED_CMD + def usbHandleSendNspHeader(cmd_block: bytes) -> int: global g_nspTransferMode, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath @@ -967,7 +986,6 @@ def usbHandleSendNspHeader(cmd_block: bytes) -> int: # Write NSP header. g_nspFile.seek(0) g_nspFile.write(cmd_block) - g_nspFile.close() g_logger.debug(f'Successfully wrote 0x{nsp_header_size:X}-byte long NSP header to "{g_nspFilePath}".\n') @@ -981,15 +999,41 @@ def usbHandleEndSession(cmd_block: bytes) -> int: g_logger.debug(f'Received EndSession ({USB_CMD_END_SESSION:02X}) command.') return USB_STATUS_SUCCESS +def usbHandleStartExtractedFsDump(cmd_block: bytes) -> int: + #assert g_logger is not None + + g_logger.debug(f'Received StartExtractedFsDump ({USB_CMD_START_EXTRACTED_FS_DUMP:02X}) command.') + + if g_nspTransferMode: + g_logger.error('StartExtractedFsDump received mid NSP transfer.') + return USB_STATUS_MALFORMED_CMD + + # Parse command block. + (extracted_fs_size, extracted_fs_root_path) = struct.unpack_from(f' int: + #assert g_logger is not None + g_logger.debug(f'Received EndExtractedFsDump ({USB_CMD_END_EXTRACTED_FS_DUMP:02X}) command.') + g_logger.info(f'Finished extracted FS dump.') + return USB_STATUS_SUCCESS + def usbCommandHandler() -> None: #assert g_logger is not None - # CancelFileTransfer is handled in usbHandleSendFileProperties(). cmd_dict = { - USB_CMD_START_SESSION: usbHandleStartSession, - USB_CMD_SEND_FILE_PROPERTIES: usbHandleSendFileProperties, - USB_CMD_SEND_NSP_HEADER: usbHandleSendNspHeader, - USB_CMD_END_SESSION: usbHandleEndSession + USB_CMD_START_SESSION: usbHandleStartSession, + USB_CMD_SEND_FILE_PROPERTIES: usbHandleSendFileProperties, + USB_CMD_CANCEL_FILE_TRANSFER: usbHandleCancelFileTransfer, + USB_CMD_SEND_NSP_HEADER: usbHandleSendNspHeader, + USB_CMD_END_SESSION: usbHandleEndSession, + USB_CMD_START_EXTRACTED_FS_DUMP: usbHandleStartExtractedFsDump, + USB_CMD_END_EXTRACTED_FS_DUMP: usbHandleEndExtractedFsDump } # Get device endpoints. @@ -1050,7 +1094,8 @@ def usbCommandHandler() -> None: # Verify command block size. 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): + (cmd_id == USB_CMD_SEND_NSP_HEADER and not cmd_block_size) or \ + (cmd_id == USB_CMD_START_EXTRACTED_FS_DUMP and cmd_block_size != USB_CMD_BLOCK_SIZE_START_EXTRACTED_FS_DUMP): 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 diff --git a/include/core/usb.h b/include/core/usb.h index 2cc86d6..1d6d973 100644 --- a/include/core/usb.h +++ b/include/core/usb.h @@ -81,6 +81,13 @@ void usbCancelFileTransfer(void); /// 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); +/// Informs the host device that an extracted filesystem dump (e.g. HFS, PFS, RomFS) is about to begin. +bool usbStartExtractedFsDump(u64 extracted_fs_size, const char *extracted_fs_root_path); + +/// Informs the host device that a previously started filesystem dump (via usbStartExtractedFsDump()) has finished. +/// This is only issued after all extracted file entries have been successfully transferred to the host device. +void usbEndExtractedFsDump(void); + #ifdef __cplusplus } #endif diff --git a/source/core/usb.c b/source/core/usb.c index 29fc67d..dc29efd 100644 --- a/source/core/usb.c +++ b/source/core/usb.c @@ -57,12 +57,14 @@ /* Type definitions. */ typedef enum { - UsbCommandType_StartSession = 0, - UsbCommandType_SendFileProperties = 1, - UsbCommandType_CancelFileTransfer = 2, - UsbCommandType_SendNspHeader = 3, - UsbCommandType_EndSession = 4, - UsbCommandType_Count = 5 ///< Total values supported by this enum. + UsbCommandType_StartSession = 0, + UsbCommandType_SendFileProperties = 1, + UsbCommandType_CancelFileTransfer = 2, + UsbCommandType_SendNspHeader = 3, + UsbCommandType_EndSession = 4, + UsbCommandType_StartExtractedFsDump = 5, + UsbCommandType_EndExtractedFsDump = 6, + UsbCommandType_Count = 7 ///< Total values supported by this enum. } UsbCommandType; typedef struct { @@ -90,11 +92,19 @@ typedef struct { u32 filename_length; u32 nsp_header_size; char filename[FS_MAX_PATH]; - u8 reserved_2[0xF]; + u8 reserved[0xF]; } UsbCommandSendFileProperties; NXDT_ASSERT(UsbCommandSendFileProperties, 0x320); +typedef struct { + u64 extracted_fs_size; + char extracted_fs_root_path[FS_MAX_PATH]; + u8 reserved[0x6]; +} UsbCommandStartExtractedFsDump; + +NXDT_ASSERT(UsbCommandStartExtractedFsDump, 0x310); + typedef enum { ///< Expected response code. UsbStatusType_Success = 0, @@ -211,7 +221,7 @@ static void usbEndSession(void); NX_INLINE void usbPrepareCommandHeader(u32 cmd, u32 cmd_block_size); static bool usbSendCommand(void); -#if LOG_LEVEL <= LOG_LEVEL_ERROR +#if LOG_LEVEL <= LOG_LEVEL_INFO static void usbLogStatusDetail(u32 status); #endif @@ -363,8 +373,8 @@ bool usbSendFileData(void *data, u64 data_size) void *buf = NULL; bool zlt_required = false; - if (!g_usbTransferBuffer || !g_usbInterfaceInit || !g_usbHostAvailable || !g_usbSessionStarted || !g_usbTransferRemainingSize || !data || !data_size || data_size > USB_TRANSFER_BUFFER_SIZE || \ - data_size > g_usbTransferRemainingSize) + if (!g_usbTransferBuffer || !g_usbInterfaceInit || !g_usbHostAvailable || !g_usbSessionStarted || !g_usbTransferRemainingSize || !data || !data_size || \ + data_size > USB_TRANSFER_BUFFER_SIZE || data_size > g_usbTransferRemainingSize) { LOG_MSG_ERROR("Invalid parameters!"); goto end; @@ -430,7 +440,7 @@ bool usbSendFileData(void *data, u64 data_size) } ret = (cmd_status->status == UsbStatusType_Success); -#if LOG_LEVEL <= LOG_LEVEL_ERROR +#if LOG_LEVEL <= LOG_LEVEL_INFO if (!ret) usbLogStatusDetail(cmd_status->status); #endif } @@ -474,8 +484,8 @@ bool usbSendNspHeader(void *nsp_header, u32 nsp_header_size) SCOPED_LOCK(&g_usbInterfaceMutex) { - if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || g_usbTransferRemainingSize || !g_nspTransferMode || !nsp_header || !nsp_header_size || \ - nsp_header_size > (USB_TRANSFER_BUFFER_SIZE - sizeof(UsbCommandHeader))) + if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || g_usbTransferRemainingSize || !g_nspTransferMode || !nsp_header || \ + !nsp_header_size || nsp_header_size > (USB_TRANSFER_BUFFER_SIZE - sizeof(UsbCommandHeader))) { LOG_MSG_ERROR("Invalid parameters!"); break; @@ -495,6 +505,45 @@ bool usbSendNspHeader(void *nsp_header, u32 nsp_header_size) return ret; } +bool usbStartExtractedFsDump(u64 extracted_fs_size, const char *extracted_fs_root_path) +{ + bool ret = false; + + SCOPED_LOCK(&g_usbInterfaceMutex) + { + if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || g_usbTransferRemainingSize || g_nspTransferMode || !extracted_fs_size || \ + !extracted_fs_root_path || !*extracted_fs_root_path) break; + + /* Prepare command data. */ + usbPrepareCommandHeader(UsbCommandType_StartExtractedFsDump, (u32)sizeof(UsbCommandStartExtractedFsDump)); + + UsbCommandStartExtractedFsDump *cmd_block = (UsbCommandStartExtractedFsDump*)(g_usbTransferBuffer + sizeof(UsbCommandHeader)); + memset(cmd_block, 0, sizeof(UsbCommandStartExtractedFsDump)); + + cmd_block->extracted_fs_size = extracted_fs_size; + snprintf(cmd_block->extracted_fs_root_path, sizeof(cmd_block->extracted_fs_root_path), "%s", extracted_fs_root_path); + + /* Send command. */ + ret = usbSendCommand(); + } + + return ret; +} + +void usbEndExtractedFsDump(void) +{ + SCOPED_LOCK(&g_usbInterfaceMutex) + { + if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || g_usbTransferRemainingSize || g_nspTransferMode) break; + + /* Prepare command data. */ + usbPrepareCommandHeader(UsbCommandType_EndExtractedFsDump, 0); + + /* Send command. We don't care about the result here. */ + usbSendCommand(); + } +} + static bool usbCreateDetectionThread(void) { if (!utilsCreateThread(&g_usbDetectionThread, usbDetectionThreadFunc, NULL, 1)) @@ -641,7 +690,7 @@ static void usbEndSession(void) NX_INLINE void usbPrepareCommandHeader(u32 cmd, u32 cmd_block_size) { - if (cmd > UsbCommandType_EndSession) return; + if (cmd >= UsbCommandType_Count) return; UsbCommandHeader *cmd_header = (UsbCommandHeader*)g_usbTransferBuffer; memset(cmd_header, 0, sizeof(UsbCommandHeader)); cmd_header->magic = __builtin_bswap32(USB_CMD_HEADER_MAGIC); @@ -658,6 +707,11 @@ static bool usbSendCommand(void) u32 cmd = cmd_header->cmd; #endif +#if LOG_LEVEL <= LOG_LEVEL_INFO + UsbCommandHeader cmd_header_bkp = {0}; + memcpy(&cmd_header_bkp, cmd_header, sizeof(UsbCommandHeader)); +#endif + UsbStatus *cmd_status = (UsbStatus*)g_usbTransferBuffer; u32 status = UsbStatusType_Success; @@ -723,8 +777,17 @@ static bool usbSendCommand(void) ret = ((status = cmd_status->status) == UsbStatusType_Success); end: -#if LOG_LEVEL <= LOG_LEVEL_ERROR - if (!ret) usbLogStatusDetail(status); +#if LOG_LEVEL <= LOG_LEVEL_INFO + if (!ret) + { + usbLogStatusDetail(status); + + if (status > UsbStatusType_ReadStatusFailed) + { + LOG_DATA_INFO(&cmd_header_bkp, sizeof(cmd_header_bkp), "USB command header dump:"); + if (cmd_block_size) LOG_DATA_INFO(g_usbTransferBuffer, cmd_block_size, "USB command block dump:"); + } + } #endif return ret;