mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2025-02-01 22:35:00 +00:00
Minor fixes.
* host: log received command headers and blocks in hexadecimal form (verbose mode only). * host: report USB protocol version under non-verbose mode. * host: properly parse filename field from SendFileProperties commands by actually using the filename length field from the command header. * host: validate command block size field for CancelFileTransfer commands. * bis_storage: ignore mount errors if no FAT filesystem can be found in any BIS partition(s). * utils: update utilsGeneratePath() logic to fix a UTF-8 codepoint truncation issue whenever a file extension was manually provided.
This commit is contained in:
parent
b5750277d5
commit
8dcb46365e
3 changed files with 83 additions and 48 deletions
|
@ -331,6 +331,7 @@ g_taskbar: Any = None
|
||||||
g_usbEpIn: Any = None
|
g_usbEpIn: Any = None
|
||||||
g_usbEpOut: Any = None
|
g_usbEpOut: Any = None
|
||||||
g_usbEpMaxPacketSize: int = 0
|
g_usbEpMaxPacketSize: int = 0
|
||||||
|
g_usbVersion: str = ''
|
||||||
|
|
||||||
g_nxdtVersionMajor: int = 0
|
g_nxdtVersionMajor: int = 0
|
||||||
g_nxdtVersionMinor: int = 0
|
g_nxdtVersionMinor: int = 0
|
||||||
|
@ -634,7 +635,7 @@ def utilsGetSizeUnitAndDivisor(size: int) -> tuple[str, int]:
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def usbGetDeviceEndpoints() -> bool:
|
def usbGetDeviceEndpoints() -> bool:
|
||||||
global g_usbEpIn, g_usbEpOut, g_usbEpMaxPacketSize
|
global g_usbEpIn, g_usbEpOut, g_usbEpMaxPacketSize, g_usbVersion
|
||||||
|
|
||||||
assert g_logger is not None
|
assert g_logger is not None
|
||||||
|
|
||||||
|
@ -642,7 +643,6 @@ def usbGetDeviceEndpoints() -> bool:
|
||||||
prev_dev: usb.core.Device | None = None
|
prev_dev: usb.core.Device | None = None
|
||||||
usb_ep_in_lambda = lambda ep: usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN
|
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_ep_out_lambda = lambda ep: usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_OUT
|
||||||
usb_version = 0
|
|
||||||
|
|
||||||
if g_cliMode:
|
if g_cliMode:
|
||||||
g_logger.info(SERVER_START_MSG)
|
g_logger.info(SERVER_START_MSG)
|
||||||
|
@ -706,12 +706,12 @@ def usbGetDeviceEndpoints() -> bool:
|
||||||
|
|
||||||
# Save endpoint max packet size and USB version.
|
# Save endpoint max packet size and USB version.
|
||||||
g_usbEpMaxPacketSize = g_usbEpIn.wMaxPacketSize
|
g_usbEpMaxPacketSize = g_usbEpIn.wMaxPacketSize
|
||||||
usb_version = cur_dev.bcdUSB
|
g_usbVersion = f'{cur_dev.bcdUSB >> 8}.{(cur_dev.bcdUSB & 0xFF) >> 4}'
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
g_logger.debug(f'Successfully retrieved USB endpoints! (bus {cur_dev.bus}, address {cur_dev.address}).')
|
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')
|
g_logger.debug(f'Max packet size: 0x{g_usbEpMaxPacketSize:X}. BCD USB: 0x{cur_dev.bcdUSB:04X}.\n')
|
||||||
|
|
||||||
if g_cliMode:
|
if g_cliMode:
|
||||||
g_logger.info(SERVER_STOP_MSG)
|
g_logger.info(SERVER_STOP_MSG)
|
||||||
|
@ -770,7 +770,7 @@ def usbHandleStartSession(cmd_block: bytes) -> int:
|
||||||
g_nxdtAbiVersionMinor = (abi_version & 0x0F)
|
g_nxdtAbiVersionMinor = (abi_version & 0x0F)
|
||||||
|
|
||||||
# Print client info.
|
# Print client info.
|
||||||
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')
|
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}), USB {g_usbVersion}.\n')
|
||||||
|
|
||||||
# Check if we support this ABI version.
|
# Check if we support this ABI version.
|
||||||
if (g_nxdtAbiVersionMajor != USB_ABI_VERSION_MAJOR) or (g_nxdtAbiVersionMinor != USB_ABI_VERSION_MINOR):
|
if (g_nxdtAbiVersionMajor != USB_ABI_VERSION_MAJOR) or (g_nxdtAbiVersionMinor != USB_ABI_VERSION_MINOR):
|
||||||
|
@ -792,8 +792,9 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
|
||||||
g_logger.debug(f'Received SendFileProperties ({USB_CMD_SEND_FILE_PROPERTIES:02X}) command.')
|
g_logger.debug(f'Received SendFileProperties ({USB_CMD_SEND_FILE_PROPERTIES:02X}) command.')
|
||||||
|
|
||||||
# Parse command block.
|
# Parse command block.
|
||||||
(file_size, filename_length, nsp_header_size, raw_filename) = struct.unpack_from(f'<QII{USB_FILE_PROPERTIES_MAX_NAME_LENGTH}s', cmd_block, 0)
|
(file_size, filename_length, nsp_header_size) = struct.unpack_from('<QII', cmd_block, 0)
|
||||||
filename = raw_filename.decode('utf-8').strip('\x00')
|
raw_filename = struct.unpack_from(f'<{filename_length}s', cmd_block, 16)[0]
|
||||||
|
filename = raw_filename.decode('utf-8')
|
||||||
|
|
||||||
# Print info.
|
# Print info.
|
||||||
dbg_str = f'File size: 0x{file_size:X} | Filename length: 0x{filename_length:X}'
|
dbg_str = f'File size: 0x{file_size:X} | Filename length: 0x{filename_length:X}'
|
||||||
|
@ -956,12 +957,12 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
|
||||||
|
|
||||||
# Check if we're dealing with a CancelFileTransfer command.
|
# Check if we're dealing with a CancelFileTransfer command.
|
||||||
if chunk_size == USB_CMD_HEADER_SIZE:
|
if chunk_size == USB_CMD_HEADER_SIZE:
|
||||||
(magic, cmd_id, _) = struct.unpack_from('<4sII', chunk, 0)
|
(magic, cmd_id, cmd_block_size, _) = struct.unpack_from('<4sIII', chunk, 0)
|
||||||
if (magic == USB_MAGIC_WORD) and (cmd_id == USB_CMD_CANCEL_FILE_TRANSFER):
|
if (magic == USB_MAGIC_WORD) and (cmd_id == USB_CMD_CANCEL_FILE_TRANSFER) and (cmd_block_size == 0):
|
||||||
# Cancel file transfer.
|
# Cancel file transfer.
|
||||||
cancelTransfer()
|
cancelTransfer()
|
||||||
|
|
||||||
g_logger.debug(f'Received CancelFileTransfer ({USB_CMD_CANCEL_FILE_TRANSFER:02X}) command.')
|
g_logger.debug(f'Received CancelFileTransfer ({USB_CMD_CANCEL_FILE_TRANSFER:02X}) command:\n{bytes.hex(chunk, " ", 1)}\n')
|
||||||
g_logger.warning('Transfer cancelled.')
|
g_logger.warning('Transfer cancelled.')
|
||||||
|
|
||||||
# Let the command handler take care of sending the status response for us.
|
# Let the command handler take care of sending the status response for us.
|
||||||
|
@ -998,7 +999,7 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
|
||||||
def usbHandleCancelFileTransfer(cmd_block: bytes) -> int:
|
def usbHandleCancelFileTransfer(cmd_block: bytes) -> int:
|
||||||
assert g_logger is not None
|
assert g_logger is not None
|
||||||
|
|
||||||
g_logger.debug(f'Received CancelFileTransfer ({USB_CMD_START_SESSION:02X}) command.')
|
g_logger.debug(f'Received CancelFileTransfer ({USB_CMD_CANCEL_FILE_TRANSFER:02X}) command.')
|
||||||
|
|
||||||
if g_nspTransferMode:
|
if g_nspTransferMode:
|
||||||
if (g_nspSize > USB_TRANSFER_THRESHOLD) and (g_progressBarWindow is not None):
|
if (g_nspSize > USB_TRANSFER_THRESHOLD) and (g_progressBarWindow is not None):
|
||||||
|
@ -1112,8 +1113,10 @@ def usbCommandHandler() -> None:
|
||||||
g_logger.error(f'Failed to read 0x{USB_CMD_HEADER_SIZE:X}-byte long command header!')
|
g_logger.error(f'Failed to read 0x{USB_CMD_HEADER_SIZE:X}-byte long command header!')
|
||||||
break
|
break
|
||||||
|
|
||||||
|
g_logger.debug(f'Received command header data:\n{bytes.hex(cmd_header, " ", 1)}\n')
|
||||||
|
|
||||||
# Parse command header.
|
# Parse command header.
|
||||||
(magic, cmd_id, cmd_block_size) = struct.unpack_from('<4sII', cmd_header, 0)
|
(magic, cmd_id, cmd_block_size, _) = struct.unpack_from('<4sIII', cmd_header, 0)
|
||||||
|
|
||||||
# Read command block right away (if needed).
|
# Read command block right away (if needed).
|
||||||
# nxdumptool expects us to read it right after sending the command header.
|
# nxdumptool expects us to read it right after sending the command header.
|
||||||
|
@ -1130,6 +1133,8 @@ def usbCommandHandler() -> None:
|
||||||
g_logger.error(f'Failed to read 0x{cmd_block_size:X}-byte long command block for command ID {cmd_id:02X}!')
|
g_logger.error(f'Failed to read 0x{cmd_block_size:X}-byte long command block for command ID {cmd_id:02X}!')
|
||||||
break
|
break
|
||||||
|
|
||||||
|
g_logger.debug(f'Received command block data:\n{bytes.hex(cmd_block, " ", 1)}\n')
|
||||||
|
|
||||||
# Verify magic word.
|
# Verify magic word.
|
||||||
if magic != USB_MAGIC_WORD:
|
if magic != USB_MAGIC_WORD:
|
||||||
g_logger.error('Received command header with invalid magic word!\n')
|
g_logger.error('Received command header with invalid magic word!\n')
|
||||||
|
@ -1146,6 +1151,7 @@ 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_CANCEL_FILE_TRANSFER and cmd_block_size) or \
|
||||||
(cmd_id == USB_CMD_SEND_NSP_HEADER and not cmd_block_size) or \
|
(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):
|
(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')
|
||||||
|
|
|
@ -181,7 +181,7 @@ static bool bisStorageMountPartition(u8 bis_partition_id)
|
||||||
BisStorageFatFsContext *bis_fatfs_ctx = NULL;
|
BisStorageFatFsContext *bis_fatfs_ctx = NULL;
|
||||||
Result rc = 0;
|
Result rc = 0;
|
||||||
FRESULT fr = FR_OK;
|
FRESULT fr = FR_OK;
|
||||||
bool success = false;
|
bool success = false, empty_partition = false;
|
||||||
|
|
||||||
/* Check if we have already mounted this eMMC partition. */
|
/* Check if we have already mounted this eMMC partition. */
|
||||||
if (BIS_STORAGE_FATFS_CTX(bis_partition_id))
|
if (BIS_STORAGE_FATFS_CTX(bis_partition_id))
|
||||||
|
@ -222,6 +222,11 @@ static bool bisStorageMountPartition(u8 bis_partition_id)
|
||||||
if (fr != FR_OK)
|
if (fr != FR_OK)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to mount %s partition via FatFs! (%u).", bis_fatfs_ctx->gpt_name, fr);
|
LOG_MSG_ERROR("Failed to mount %s partition via FatFs! (%u).", bis_fatfs_ctx->gpt_name, fr);
|
||||||
|
|
||||||
|
/* Add an exception for partitions that don't hold a valid FAT volume. */
|
||||||
|
/* TODO: find out why some of these partitions are empty in some consoles. */
|
||||||
|
if (fr == FR_NO_FILESYSTEM) empty_partition = true;
|
||||||
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +245,7 @@ end:
|
||||||
{
|
{
|
||||||
bisStorageFreeFatFsContext(&bis_fatfs_ctx);
|
bisStorageFreeFatFsContext(&bis_fatfs_ctx);
|
||||||
BIS_STORAGE_FATFS_CTX(bis_partition_id) = NULL;
|
BIS_STORAGE_FATFS_CTX(bis_partition_id) = NULL;
|
||||||
|
if (empty_partition) success = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
|
|
|
@ -1066,35 +1066,52 @@ char *utilsGeneratePath(const char *prefix, const char *filename, const char *ex
|
||||||
/* Get current path element size. */
|
/* Get current path element size. */
|
||||||
size_t element_size = (ptr2 ? (size_t)(ptr2 - ptr1) : (path_len - (size_t)(ptr1 - path)));
|
size_t element_size = (ptr2 ? (size_t)(ptr2 - ptr1) : (path_len - (size_t)(ptr1 - path)));
|
||||||
|
|
||||||
/* Get UTF-8 string limit. */
|
/* Short-circuit: proceed onto the next path element right away if the current one fits within our max filename length. */
|
||||||
/* Use our max filename length as the byte count limit. */
|
if (element_size <= max_filename_len)
|
||||||
size_t last_cp_pos = utilsGetUtf8StringLimit(ptr1, element_size, max_filename_len);
|
|
||||||
if (last_cp_pos < element_size)
|
|
||||||
{
|
{
|
||||||
if (ptr2)
|
/* Update pointer. */
|
||||||
{
|
ptr1 = ptr2;
|
||||||
/* Truncate current element by moving the rest of the path to the current position. */
|
continue;
|
||||||
memmove(ptr1 + last_cp_pos, ptr2, path_len - (size_t)(ptr2 - path));
|
|
||||||
|
|
||||||
/* Update pointer. */
|
|
||||||
ptr2 -= (element_size - last_cp_pos);
|
|
||||||
} else
|
|
||||||
if (use_extension)
|
|
||||||
{
|
|
||||||
/* Truncate last element. Make sure to preserve the provided file extension. */
|
|
||||||
if (extension_len >= last_cp_pos)
|
|
||||||
{
|
|
||||||
LOG_MSG_ERROR("File extension length is >= truncated filename length! (0x%lX >= 0x%lX).", extension_len, last_cp_pos);
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
memmove(ptr1 + last_cp_pos - extension_len, ptr1 + element_size - extension_len, extension_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
path_len -= (element_size - last_cp_pos);
|
|
||||||
path[path_len] = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Get UTF-8 string limit. */
|
||||||
|
/* We'll use our max filename length as the byte count limit. */
|
||||||
|
/* Make sure to preserve the file extension if it was provided and if we're dealing with the last path element. */
|
||||||
|
size_t byte_limit = ((ptr2 || !use_extension) ? max_filename_len : (max_filename_len - extension_len));
|
||||||
|
|
||||||
|
size_t last_cp_pos = utilsGetUtf8StringLimit(ptr1, element_size, byte_limit);
|
||||||
|
if (last_cp_pos > byte_limit)
|
||||||
|
{
|
||||||
|
/* Something went terribly wrong somewhere. */
|
||||||
|
LOG_MSG_ERROR("Unable to appropiately determine last UTF-8 codepoint position for path element \"%.*s\" in \"%s\" (%lu >= %lu).", (int)element_size, ptr1, path, last_cp_pos, byte_limit);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prepare variables for path truncation. */
|
||||||
|
char *ptr3 = NULL;
|
||||||
|
size_t move_size = 0, diff = (element_size - last_cp_pos);
|
||||||
|
|
||||||
|
if (ptr2)
|
||||||
|
{
|
||||||
|
ptr3 = ptr2;
|
||||||
|
move_size = (path_len - (size_t)(ptr2 - path));
|
||||||
|
ptr2 = (ptr1 + last_cp_pos);
|
||||||
|
} else
|
||||||
|
if (use_extension)
|
||||||
|
{
|
||||||
|
ptr3 = (ptr1 + element_size - extension_len);
|
||||||
|
move_size = extension_len;
|
||||||
|
diff -= extension_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Truncate path element by moving the rest of the path string to the last UTF-8 codepoint position, if needed. */
|
||||||
|
if (ptr3 && move_size) memmove(ptr1 + last_cp_pos, ptr3, move_size);
|
||||||
|
|
||||||
|
/* Update path length. */
|
||||||
|
path_len -= diff;
|
||||||
|
path[path_len] = '\0';
|
||||||
|
|
||||||
|
/* Update pointer. */
|
||||||
ptr1 = ptr2;
|
ptr1 = ptr2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1546,24 +1563,30 @@ static size_t utilsGetUtf8StringLimit(const char *str, size_t str_size, size_t b
|
||||||
{
|
{
|
||||||
if (!str || !*str || !str_size || !byte_limit) return 0;
|
if (!str || !*str || !str_size || !byte_limit) return 0;
|
||||||
|
|
||||||
|
/* Short-circuit: return immediately if we have enough space to hold the full string. */
|
||||||
if (byte_limit > str_size) return str_size;
|
if (byte_limit > str_size) return str_size;
|
||||||
|
|
||||||
u32 code = 0;
|
u32 code = 0;
|
||||||
ssize_t units = 0;
|
ssize_t units = 0;
|
||||||
size_t cur_pos = 0, last_cp_pos = 0;
|
size_t cur_pos = 0;
|
||||||
const u8 *str_u8 = (const u8*)str;
|
const u8 *ptr = (const u8*)str;
|
||||||
|
|
||||||
while(cur_pos < str_size && cur_pos < byte_limit)
|
do {
|
||||||
{
|
/* Decode current codepoint. */
|
||||||
units = decode_utf8(&code, str_u8 + cur_pos);
|
units = decode_utf8(&code, ptr);
|
||||||
|
if (units < 0) break;
|
||||||
|
|
||||||
|
/* Calculate new position within the input string. */
|
||||||
|
/* Bail out immediately if we have exceeded a size limitation. */
|
||||||
size_t new_pos = (cur_pos + (size_t)units);
|
size_t new_pos = (cur_pos + (size_t)units);
|
||||||
if (units < 0 || !code || new_pos > str_size) break;
|
if (new_pos > str_size || new_pos > byte_limit) break;
|
||||||
|
|
||||||
|
/* Update current position. */
|
||||||
cur_pos = new_pos;
|
cur_pos = new_pos;
|
||||||
if (cur_pos < byte_limit) last_cp_pos = cur_pos;
|
ptr += units;
|
||||||
}
|
} while(code != 0);
|
||||||
|
|
||||||
return last_cp_pos;
|
return cur_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char utilsConvertHexDigitToBinary(char c)
|
static char utilsConvertHexDigitToBinary(char c)
|
||||||
|
|
Loading…
Add table
Reference in a new issue