1
0
Fork 0
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:
Pablo Curiel 2025-01-30 11:34:26 +01:00
parent b5750277d5
commit 8dcb46365e
3 changed files with 83 additions and 48 deletions

View file

@ -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')

View file

@ -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;

View file

@ -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)