1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-26 04:02:11 +00:00

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.
This commit is contained in:
Pablo Curiel 2023-11-11 21:39:41 +01:00
parent 95b603142a
commit dcd1f66790
5 changed files with 252 additions and 65 deletions

View file

@ -3756,15 +3756,25 @@ static void extractedHfsReadThreadFunc(void *arg)
{ {
consolePrint("failed to retrieve free space from selected device\n"); consolePrint("failed to retrieve free space from selected device\n");
shared_thread_data->read_error = true; 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"); consolePrint("dump size exceeds free space\n");
shared_thread_data->read_error = true; 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. */ /* 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); if (shared_thread_data->data_size) condvarWait(&g_readCondvar, &g_fileMutex);
mutexUnlock(&g_fileMutex); mutexUnlock(&g_fileMutex);
if (dev_idx == 1) usbEndExtractedFsDump();
consolePrint("successfully saved extracted hfs partition data to \"%s\"\n", filename); consolePrint("successfully saved extracted hfs partition data to \"%s\"\n", filename);
consoleRefresh(); consoleRefresh();
} }
@ -4131,15 +4143,25 @@ static void extractedPartitionFsReadThreadFunc(void *arg)
{ {
consolePrint("failed to retrieve free space from selected device\n"); consolePrint("failed to retrieve free space from selected device\n");
shared_thread_data->read_error = true; 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"); consolePrint("dump size exceeds free space\n");
shared_thread_data->read_error = true; 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. */ /* 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); if (shared_thread_data->data_size) condvarWait(&g_readCondvar, &g_fileMutex);
mutexUnlock(&g_fileMutex); mutexUnlock(&g_fileMutex);
if (dev_idx == 1) usbEndExtractedFsDump();
consolePrint("successfully saved extracted partitionfs section data to \"%s\"\n", filename); consolePrint("successfully saved extracted partitionfs section data to \"%s\"\n", filename);
consoleRefresh(); consoleRefresh();
} }
@ -4437,15 +4461,25 @@ static void extractedRomFsReadThreadFunc(void *arg)
{ {
consolePrint("failed to retrieve free space from selected device\n"); consolePrint("failed to retrieve free space from selected device\n");
shared_thread_data->read_error = true; 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"); consolePrint("dump size exceeds free space\n");
shared_thread_data->read_error = true; 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. */ /* 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); if (shared_thread_data->data_size) condvarWait(&g_readCondvar, &g_fileMutex);
mutexUnlock(&g_fileMutex); mutexUnlock(&g_fileMutex);
if (dev_idx == 1) usbEndExtractedFsDump();
consolePrint("successfully saved extracted romfs section data to \"%s\"\n", filename); consolePrint("successfully saved extracted romfs section data to \"%s\"\n", filename);
consoleRefresh(); consoleRefresh();
} }
@ -5293,6 +5329,19 @@ static void nspThreadFunc(void *arg)
// update clean hash calculation // update clean hash calculation
sha256ContextUpdate(&clean_sha256_ctx, buf, blksize); 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) if (dirty_header)
{ {
// write re-encrypted headers // write re-encrypted headers
@ -5334,17 +5383,9 @@ static void nspThreadFunc(void *arg)
} }
} }
// get hashes // get dirty hash
sha256ContextGetHash(&clean_sha256_ctx, clean_sha256_hash);
sha256ContextGetHash(&dirty_sha256_ctx, dirty_sha256_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) if (memcmp(clean_sha256_hash, dirty_sha256_hash, SHA256_HASH_SIZE) != 0)
{ {
// update content id and hash // update content id and hash

View file

@ -21,6 +21,8 @@ Unless stated otherwise, the reader must assume all integer fields in the docume
* [CancelFileTransfer](#cancelfiletransfer). * [CancelFileTransfer](#cancelfiletransfer).
* [SendNspHeader](#sendnspheader). * [SendNspHeader](#sendnspheader).
* [EndSession](#endsession). * [EndSession](#endsession).
* [StartExtractedFsDump](#startextractedfsdump).
* [EndExtractedFsDump](#endextractedfsdump).
* [Status response](#status-response). * [Status response](#status-response).
* [Status codes](#status-codes). * [Status codes](#status-codes).
* [NSP transfer mode](#nsp-transfer-mode). * [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 #### Command IDs
| Value | Name | Description | | Value | Name | Description |
|-------|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| |-------|-------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
| 0 | [`StartSession`](#startsession) | Starts a USB session between the target console and the USB host device. | | 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. | | 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. | | 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). | | 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. | | 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 ### Command blocks
@ -134,11 +138,11 @@ Size: 0x320 bytes.
| Offset | Size | Type | Description | | Offset | Size | Type | Description |
|--------|-------|---------------|----------------------------------------------| |--------|-------|---------------|----------------------------------------------|
| 0x000 | 0x08 | `uint64_t` | File size. | | 0x000 | 0x008 | `uint64_t` | File size. |
| 0x008 | 0x04 | `uint32_t` | Path length. | | 0x008 | 0x004 | `uint32_t` | Path length. |
| 0x00C | 0x04 | `uint32_t` | [NSP header size](#nsp-transfer-mode). | | 0x00C | 0x004 | `uint32_t` | [NSP header size](#nsp-transfer-mode). |
| 0x010 | 0x301 | `char[769]` | UTF-8 encoded path (NULL-terminated string). | | 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. 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. 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. 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. 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 ### Status response
Size: 0x10 bytes. Size: 0x10 bytes.

View file

@ -83,7 +83,7 @@ USB_DEV_MANUFACTURER = 'DarkMatterCore'
USB_DEV_PRODUCT = 'nxdumptool' USB_DEV_PRODUCT = 'nxdumptool'
# USB timeout (milliseconds). # USB timeout (milliseconds).
USB_TRANSFER_TIMEOUT = 5000 USB_TRANSFER_TIMEOUT = 10000
# USB transfer block size. # USB transfer block size.
USB_TRANSFER_BLOCK_SIZE = 0x800000 USB_TRANSFER_BLOCK_SIZE = 0x800000
@ -102,15 +102,18 @@ USB_ABI_VERSION_MINOR = 1
USB_CMD_HEADER_SIZE = 0x10 USB_CMD_HEADER_SIZE = 0x10
# USB command IDs. # USB command IDs.
USB_CMD_START_SESSION = 0 USB_CMD_START_SESSION = 0
USB_CMD_SEND_FILE_PROPERTIES = 1 USB_CMD_SEND_FILE_PROPERTIES = 1
USB_CMD_CANCEL_FILE_TRANSFER = 2 USB_CMD_CANCEL_FILE_TRANSFER = 2
USB_CMD_SEND_NSP_HEADER = 3 USB_CMD_SEND_NSP_HEADER = 3
USB_CMD_END_SESSION = 4 USB_CMD_END_SESSION = 4
USB_CMD_START_EXTRACTED_FS_DUMP = 5
USB_CMD_END_EXTRACTED_FS_DUMP = 6
# USB command block sizes. # USB command block sizes.
USB_CMD_BLOCK_SIZE_START_SESSION = 0x10 USB_CMD_BLOCK_SIZE_START_SESSION = 0x10
USB_CMD_BLOCK_SIZE_SEND_FILE_PROPERTIES = 0x320 USB_CMD_BLOCK_SIZE_SEND_FILE_PROPERTIES = 0x320
USB_CMD_BLOCK_SIZE_START_EXTRACTED_FS_DUMP = 0x310
# Max filename length (file properties). # Max filename length (file properties).
USB_FILE_PROPERTIES_MAX_NAME_LENGTH = 0x300 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: def utilsIsValueAlignedToEndpointPacketSize(value: int) -> bool:
return bool((value & (g_usbEpMaxPacketSize - 1)) == 0) 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 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. # Reset NSP transfer mode info.
g_nspTransferMode = False g_nspTransferMode = False
g_nspSize = 0 g_nspSize = 0
@ -868,9 +876,7 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
def cancelTransfer(): def cancelTransfer():
# Cancel file transfer. # Cancel file transfer.
file.close() utilsResetNspInfo(True)
os.remove(fullpath)
utilsResetNspInfo()
if use_pbar: if use_pbar:
g_progressBarWindow.end() g_progressBarWindow.end()
@ -941,6 +947,19 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
return USB_STATUS_SUCCESS 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: def usbHandleSendNspHeader(cmd_block: bytes) -> int:
global g_nspTransferMode, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath global g_nspTransferMode, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath
@ -967,7 +986,6 @@ def usbHandleSendNspHeader(cmd_block: bytes) -> int:
# Write NSP header. # Write NSP header.
g_nspFile.seek(0) g_nspFile.seek(0)
g_nspFile.write(cmd_block) 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') 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.') g_logger.debug(f'Received EndSession ({USB_CMD_END_SESSION:02X}) command.')
return USB_STATUS_SUCCESS 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'<Q{USB_FILE_PROPERTIES_MAX_NAME_LENGTH}s', cmd_block, 0)
extracted_fs_root_path = extracted_fs_root_path.decode('utf-8').strip('\x00')
g_logger.info(f'Starting extracted FS dump (size 0x{extracted_fs_size:X}, output relative path "{extracted_fs_root_path}").')
# Return status code.
return USB_STATUS_SUCCESS
def usbHandleEndExtractedFsDump(cmd_block: bytes) -> 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: def usbCommandHandler() -> None:
#assert g_logger is not None #assert g_logger is not None
# CancelFileTransfer is handled in usbHandleSendFileProperties().
cmd_dict = { cmd_dict = {
USB_CMD_START_SESSION: usbHandleStartSession, USB_CMD_START_SESSION: usbHandleStartSession,
USB_CMD_SEND_FILE_PROPERTIES: usbHandleSendFileProperties, USB_CMD_SEND_FILE_PROPERTIES: usbHandleSendFileProperties,
USB_CMD_SEND_NSP_HEADER: usbHandleSendNspHeader, USB_CMD_CANCEL_FILE_TRANSFER: usbHandleCancelFileTransfer,
USB_CMD_END_SESSION: usbHandleEndSession 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. # Get device endpoints.
@ -1050,7 +1094,8 @@ def usbCommandHandler() -> None:
# Verify command block size. # Verify command block size.
if (cmd_id == USB_CMD_START_SESSION and cmd_block_size != USB_CMD_BLOCK_SIZE_START_SESSION) or \ 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_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') 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) usbSendStatus(USB_STATUS_MALFORMED_CMD)
continue continue

View file

@ -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. /// 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); 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 #ifdef __cplusplus
} }
#endif #endif

View file

@ -57,12 +57,14 @@
/* Type definitions. */ /* Type definitions. */
typedef enum { typedef enum {
UsbCommandType_StartSession = 0, UsbCommandType_StartSession = 0,
UsbCommandType_SendFileProperties = 1, UsbCommandType_SendFileProperties = 1,
UsbCommandType_CancelFileTransfer = 2, UsbCommandType_CancelFileTransfer = 2,
UsbCommandType_SendNspHeader = 3, UsbCommandType_SendNspHeader = 3,
UsbCommandType_EndSession = 4, UsbCommandType_EndSession = 4,
UsbCommandType_Count = 5 ///< Total values supported by this enum. UsbCommandType_StartExtractedFsDump = 5,
UsbCommandType_EndExtractedFsDump = 6,
UsbCommandType_Count = 7 ///< Total values supported by this enum.
} UsbCommandType; } UsbCommandType;
typedef struct { typedef struct {
@ -90,11 +92,19 @@ typedef struct {
u32 filename_length; u32 filename_length;
u32 nsp_header_size; u32 nsp_header_size;
char filename[FS_MAX_PATH]; char filename[FS_MAX_PATH];
u8 reserved_2[0xF]; u8 reserved[0xF];
} UsbCommandSendFileProperties; } UsbCommandSendFileProperties;
NXDT_ASSERT(UsbCommandSendFileProperties, 0x320); 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 { typedef enum {
///< Expected response code. ///< Expected response code.
UsbStatusType_Success = 0, UsbStatusType_Success = 0,
@ -211,7 +221,7 @@ static void usbEndSession(void);
NX_INLINE void usbPrepareCommandHeader(u32 cmd, u32 cmd_block_size); NX_INLINE void usbPrepareCommandHeader(u32 cmd, u32 cmd_block_size);
static bool usbSendCommand(void); static bool usbSendCommand(void);
#if LOG_LEVEL <= LOG_LEVEL_ERROR #if LOG_LEVEL <= LOG_LEVEL_INFO
static void usbLogStatusDetail(u32 status); static void usbLogStatusDetail(u32 status);
#endif #endif
@ -363,8 +373,8 @@ bool usbSendFileData(void *data, u64 data_size)
void *buf = NULL; void *buf = NULL;
bool zlt_required = false; bool zlt_required = false;
if (!g_usbTransferBuffer || !g_usbInterfaceInit || !g_usbHostAvailable || !g_usbSessionStarted || !g_usbTransferRemainingSize || !data || !data_size || data_size > USB_TRANSFER_BUFFER_SIZE || \ if (!g_usbTransferBuffer || !g_usbInterfaceInit || !g_usbHostAvailable || !g_usbSessionStarted || !g_usbTransferRemainingSize || !data || !data_size || \
data_size > g_usbTransferRemainingSize) data_size > USB_TRANSFER_BUFFER_SIZE || data_size > g_usbTransferRemainingSize)
{ {
LOG_MSG_ERROR("Invalid parameters!"); LOG_MSG_ERROR("Invalid parameters!");
goto end; goto end;
@ -430,7 +440,7 @@ bool usbSendFileData(void *data, u64 data_size)
} }
ret = (cmd_status->status == UsbStatusType_Success); ret = (cmd_status->status == UsbStatusType_Success);
#if LOG_LEVEL <= LOG_LEVEL_ERROR #if LOG_LEVEL <= LOG_LEVEL_INFO
if (!ret) usbLogStatusDetail(cmd_status->status); if (!ret) usbLogStatusDetail(cmd_status->status);
#endif #endif
} }
@ -474,8 +484,8 @@ bool usbSendNspHeader(void *nsp_header, u32 nsp_header_size)
SCOPED_LOCK(&g_usbInterfaceMutex) SCOPED_LOCK(&g_usbInterfaceMutex)
{ {
if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || g_usbTransferRemainingSize || !g_nspTransferMode || !nsp_header || !nsp_header_size || \ if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || g_usbTransferRemainingSize || !g_nspTransferMode || !nsp_header || \
nsp_header_size > (USB_TRANSFER_BUFFER_SIZE - sizeof(UsbCommandHeader))) !nsp_header_size || nsp_header_size > (USB_TRANSFER_BUFFER_SIZE - sizeof(UsbCommandHeader)))
{ {
LOG_MSG_ERROR("Invalid parameters!"); LOG_MSG_ERROR("Invalid parameters!");
break; break;
@ -495,6 +505,45 @@ bool usbSendNspHeader(void *nsp_header, u32 nsp_header_size)
return ret; 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) static bool usbCreateDetectionThread(void)
{ {
if (!utilsCreateThread(&g_usbDetectionThread, usbDetectionThreadFunc, NULL, 1)) 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) 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; UsbCommandHeader *cmd_header = (UsbCommandHeader*)g_usbTransferBuffer;
memset(cmd_header, 0, sizeof(UsbCommandHeader)); memset(cmd_header, 0, sizeof(UsbCommandHeader));
cmd_header->magic = __builtin_bswap32(USB_CMD_HEADER_MAGIC); cmd_header->magic = __builtin_bswap32(USB_CMD_HEADER_MAGIC);
@ -658,6 +707,11 @@ static bool usbSendCommand(void)
u32 cmd = cmd_header->cmd; u32 cmd = cmd_header->cmd;
#endif #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; UsbStatus *cmd_status = (UsbStatus*)g_usbTransferBuffer;
u32 status = UsbStatusType_Success; u32 status = UsbStatusType_Success;
@ -723,8 +777,17 @@ static bool usbSendCommand(void)
ret = ((status = cmd_status->status) == UsbStatusType_Success); ret = ((status = cmd_status->status) == UsbStatusType_Success);
end: end:
#if LOG_LEVEL <= LOG_LEVEL_ERROR #if LOG_LEVEL <= LOG_LEVEL_INFO
if (!ret) usbLogStatusDetail(status); 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 #endif
return ret; return ret;