mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 18:26:39 +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:
parent
95b603142a
commit
dcd1f66790
5 changed files with 252 additions and 65 deletions
|
@ -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
|
||||||
|
|
|
@ -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).
|
||||||
|
@ -102,12 +104,14 @@ 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.
|
||||||
|
|
|
@ -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
|
||||||
|
@ -107,10 +107,13 @@ 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_CANCEL_FILE_TRANSFER: usbHandleCancelFileTransfer,
|
||||||
USB_CMD_SEND_NSP_HEADER: usbHandleSendNspHeader,
|
USB_CMD_SEND_NSP_HEADER: usbHandleSendNspHeader,
|
||||||
USB_CMD_END_SESSION: usbHandleEndSession
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -62,7 +62,9 @@ typedef enum {
|
||||||
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;
|
||||||
|
|
Loading…
Reference in a new issue