From a016ba92db21bf7bb454067bd143bf9dd4224575 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Fri, 19 Mar 2021 21:59:53 -0400 Subject: [PATCH] More host script changes. * Added text files with required module information, which can be used with pip. * Added a batch file to build a ZIP archive with a standalone copy of the script, which can be used to run it under Windows systems without Python. * Further tweaked the way the progress bar window works. * Debug messages are no longer printed to the log. * Rearranged some parts of the code. --- .gitignore | 7 +- host/nxdt.ico | Bin 0 -> 9662 bytes nxdt_host.pyw => host/nxdt_host.py | 253 +++++----- host/requirements-win32.txt | 2 + host/requirements.txt | 2 + usb_abi_specs.txt => host/usb_abi_specs.txt | 0 host/windows_install_deps.py | 14 + host/windows_make_standalone.bat | 10 + nsul_nxdt_patch.diff | 513 -------------------- 9 files changed, 157 insertions(+), 644 deletions(-) create mode 100644 host/nxdt.ico rename nxdt_host.pyw => host/nxdt_host.py (92%) create mode 100644 host/requirements-win32.txt create mode 100644 host/requirements.txt rename usb_abi_specs.txt => host/usb_abi_specs.txt (100%) create mode 100644 host/windows_install_deps.py create mode 100644 host/windows_make_standalone.bat delete mode 100644 nsul_nxdt_patch.diff diff --git a/.gitignore b/.gitignore index 754253b..f699370 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ build -__pycache__ -dist -nxdumptool +host/nxdt_host +host/nxdumptool *.elf *.nacp *.nro @@ -11,6 +10,6 @@ nxdumptool *.lst *.tar.bz2 *.log -*.spec +*.zip /code_templates/tmp/* /source/main.c diff --git a/host/nxdt.ico b/host/nxdt.ico new file mode 100644 index 0000000000000000000000000000000000000000..e113cce95e3232b5a0fe42a2d1ec85c8dcaa0c95 GIT binary patch literal 9662 zcmeI2%WoS+9LLw3IM5t>LvY|#OO7HMHI$YE|3Z6cFO?8d51=B!u@XmAwKPpZK@`-) z6vcr;6!M}$0;wFJBm~u%M5LTZVsB6_dEH2PIF1wlnC~nznO(2recH0bR+HIz?S8&9 z-<_G=TGL|eUsspL{yweUepb^CYnt`~3oJ`}jRoZM;~7o+slyCrhNkr-5)^KT)6w_| zdO{m!;TCnol97gR+0DLqsq=Z(Cr9a!mWo_M6#Td**U5Q}acqU4tv zBJ@(!{8B?iUTOxvMMKD5Y8JoL5VDt=$uBj8=%r@!TQr2=rB?7u4Iy}`Rs2#zKrgkD zU%mGspI%b0;rIOAixI^X0BCx^n3f57sh&`z7l&yzfPSot>rM z)4$R6-uEnFZ*PyjNG0XoHaR#*dM-;hKlzZ?TVG$7Wnl2TUkb{v>scE3w4Zi&cj@Zo z%QP`IMwLp1Gk!VznPe=LN<43KbAxbvJ2fTC1cTJz!Aqj8pZJ^6QO*P&wf&h>r)Xh5 zLn=SUh+YPf>%Kt1v8m{|IYH;up-cRwaZH|>c4QhXhbF5{8AM+Cq^uKzDx^CinUcxsH ztld3q9}=bpl^=Y5{iAA=<6!d=*MM;dQG*lz58v0$vnrpPbE(1RB~t_D=Wx7~%jF2c z4U^Y8`(7Hhd>ixQCE@4Ei3!R2L0_N9mT`G|Tb6^jTrSHpul%SF4c+WJpZ)uuS1(PB zud!>alARxU*Ln+_5$C;E3p6!3$u%H;|BAuep@JWNvrW8)A9|pnBR0gY+t^Eu_+LMF zjvhQHaaO~q(5uIfQ?Xd#>AzS$%Kh^v9QlO?H}g^>ez2WAeVUg3-sa4i%odN4Mt%@> zw6*(3O}p`u8-CnpZf=e)yqV|hZ(YdqbWrw2{Bj)FcjS6r68yj1yC?fHIWWLEulE&X z-s0jSojvoA=OaeC8T?X1oi$GItIy%m(h}!P4i-7%8|Tln_W|=6$Y#sDY++%GkRMco z>X(DgO)u51(}Ev!OfFYud$%fq8|md`bIyb|r2G4eG&(XuH$Dzy?lJc|2Qk~rqy{!G ziGEZ0!6?EzZ|C{?rRzGqa^fGJk9SD4+uAapVdSG+T+2^vXKa+_8sH_yflw;#(c0QB zZEkGv^FpD5?xr8_-sR;h%R1%r_%2bEZO{&Ba@(8;1kdI4?Y2 z>!#Cbf`+U. * @@ -20,20 +20,21 @@ * along with this program. If not, see . """ -# This script depends on Pillow, PyUSB and tqdm. You can install them with `pip install Pillow pyusb tqdm`. +# This script depends on PyUSB and tqdm. +# Optionally, comtypes may also be installed under Windows to provide taskbar progress functionality. + +# Use `pip -r requirements.txt` under Linux or MacOS to install these dependencies. +# Windows users may just double-click `windows_install_deps.py` to achieve the same result. # libusb needs to be installed as well. PyUSB uses it as its USB backend. Otherwise, a NoBackend exception will be raised while calling PyUSB functions. # Under Windows, the recommended way to do this is by installing the libusb driver with Zadig (https://zadig.akeo.ie). This is a common step in Switch modding guides. # Under MacOS, use `brew install libusb` to install libusb via Homebrew. # Under Linux, you should be good to go from the start. If not, just use the package manager from your distro to install libusb. -# Optionally, comtypes may also be installed under Windows to provide taskbar progress functionality. You can install it with `pip install comtypes`. - import os import platform import threading import traceback -import ctypes import logging import queue import shutil @@ -50,8 +51,6 @@ from tkinter import filedialog, messagebox, font, scrolledtext from tqdm import tqdm import base64 -import io -from PIL import Image, ImageTk # Scaling factors. WINDOWS_SCALING_FACTOR = 96.0 @@ -67,10 +66,6 @@ APP_VERSION = '0.2' # Copyright year. COPYRIGHT_YEAR = '2021' -# Messages displayed as labels. -SERVER_START_MSG = 'Please connect a Nintendo Switch console running nxdumptool.' -SERVER_STOP_MSG = 'Exit nxdumptool on your console or disconnect it at any time to stop the server.' - # USB VID/PID pair. USB_DEV_VID = 0x057E USB_DEV_PID = 0x3000 @@ -86,7 +81,7 @@ USB_TRANSFER_TIMEOUT = 5000 USB_TRANSFER_BLOCK_SIZE = 0x800000 # USB transfer threshold. Used to determine whether a progress bar should be displayed or not. -USB_TRANSFER_THRESHOLD = round(float(USB_TRANSFER_BLOCK_SIZE) * 2.5) +USB_TRANSFER_THRESHOLD = (USB_TRANSFER_BLOCK_SIZE * 4) # USB command header/status magic word. USB_MAGIC_WORD = b'NXDT' @@ -119,11 +114,17 @@ USB_STATUS_UNSUPPORTED_ABI_VERSION = 6 USB_STATUS_MALFORMED_CMD = 7 USB_STATUS_HOST_IO_ERROR = 8 +# Messages displayed as labels. +SERVER_START_MSG = 'Please connect a Nintendo Switch console running {}.'.format(USB_DEV_PRODUCT) +SERVER_STOP_MSG = 'Exit {} on your console or disconnect it at any time to stop the server.'.format(USB_DEV_PRODUCT) + # Default directory paths. INITIAL_DIR = os.path.abspath(os.path.dirname(__file__)) DEFAULT_DIR = (INITIAL_DIR + os.path.sep + USB_DEV_PRODUCT) -# Application icon. +# Application icon (PNG). +# Embedded to load it as the icon for all windows using PhotoImage (which doesn't support ICO files) + wm_iconphoto. +# iconbitmap supports external ICO files, but it's not capable of setting the icon to all child windows. APP_ICON = b'iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAABfVelRYdFJhdyBw' + \ b'cm9maWxlIHR5cGUgZXhpZgAAeNrNmll23TiWRf8xihoC+mY4uGjWqhnU8GsfPjV2SJGpcuRHWbYoU3wkcJvTAHTnf/77uv/iT83Ru1xar6NWz5888oiTH7p//RnP9+Dz' + \ b'8/39T3j7/tt59/Fj5Jg4ptcv2nz71OR8+fzA+zOC/X7e9bffxP52o/cnx9ch6cn6ef06SM7H1/mQ32403i6oo7dfh2pvH1jHfw7l7V/9fZLP/92vJ3IjSrvwoBTjSSF5' + \ @@ -206,10 +207,10 @@ APP_ICON = b'iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAAR b'rAQgrTb2ev5uZjwAR6uNNWY0AKVsI2XGAlDSNlJmJAClbSPF6QE4wzZSnBqAs2wjxWkBONM2Upzyrc62jRTFv5k+7vMzjZA/4O1Os80EwP/vHLXv3BH8dQAAAABJRU5E' + \ b'rkJggg==' -# Taskbar Type Library (TLB) object. -TASKBAR_TLB_NAME = 'taskbar.tlb' +# Taskbar Type Library (TLB). Used under Windows 7 or greater. +TASKBAR_LIB_NAME = 'TaskbarLib.tlb' -TASKBAR_TLB = b'TVNGVAIAAQAAAAAACQQAAAAAAABBAAAAAQAAAAAAAAAOAAAA/////wAAAAAAAAAATgAAADMDAAAAAAAA/////xgAAAAgAAAAgAAAAP////8AAAAAAAAAAGQAAADIAAAA' + \ +TASKBAR_LIB = b'TVNGVAIAAQAAAAAACQQAAAAAAABBAAAAAQAAAAAAAAAOAAAA/////wAAAAAAAAAATgAAADMDAAAAAAAA/////xgAAAAgAAAAgAAAAP////8AAAAAAAAAAGQAAADIAAAA' + \ b'LAEAAJABAAD0AQAAWAIAALwCAAAgAwAAhAMAAOgDAABMBAAAsAQAABQFAAB8AQAAeAUAAP////8PAAAA/////wAAAAD/////DwAAAP////8AAAAA/////w8AAABMCAAA' + \ b'EAAAAP////8PAAAA9AYAAIAAAAD/////DwAAAHQHAADYAAAA/////w8AAABcCAAAAAIAAP////8PAAAAXAoAAEQHAAD/////DwAAAP////8AAAAA/////w8AAACgEQAA' + \ b'iAAAAP////8PAAAAKBIAACAAAAD/////DwAAAEgSAABUAAAA/////w8AAACcEgAAJAAAAP////8PAAAA/////wAAAAD/////DwAAAP////8AAAAA/////w8AAAAjIgAA' + \ @@ -282,32 +283,6 @@ TASKBAR_TLB = b'TVNGVAIAAQAAAAAACQQAAAAAAABBAAAAAQAAAAAAAAAOAAAA/////wAAAAAAAAAA b'AAAAABQAAQADAAOAAAAAAAAAJAAEAAAAFAACAAMAA4AAAAAAAAAkAAgAAAAUAAMAAwADgAAAAAAAACQADAAAAAAAAEABAABAAgAAQAMAAEC0BgAAxAYAANQGAADoBgAA' + \ b'AAAAABQAAAAoAAAAPAAAAA==' -# Setup logger object. -g_Logger = logging.getLogger() - -# Setup thread event. -g_stopEvent = threading.Event() - -# Setup Windows taskbar object (if needed). -g_tlb = g_taskbar = None -if os.name == 'nt': - g_tlb = open(TASKBAR_TLB_NAME, 'wb') - g_tlb.write(base64.b64decode(TASKBAR_TLB)) - g_tlb.close() - g_tlb = None - - try: - import comtypes.client as cc - - g_tlb = cc.GetModule(TASKBAR_TLB_NAME) - - g_taskbar = cc.CreateObject('{56FDF344-FD6D-11D0-958A-006097C9A090}', interface=g_tlb.ITaskbarList3) - g_taskbar.HrInit() - except: - traceback.print_exc() - - os.remove(TASKBAR_TLB_NAME) - # Reference: https://beenje.github.io/blog/posts/logging-to-a-tkinter-scrolledtext-widget. class LogQueueHandler(logging.Handler): def __init__(self, log_queue): @@ -379,9 +354,7 @@ class ProgressBarWindow: self.withdrawn = True if window_title: self.tk_window.title(window_title) - self.tk_window.resizable(window_resize, window_resize) - if window_protocol: self.tk_window.protocol('WM_DELETE_WINDOW', window_protocol) pbar_frame = ttk.Frame(self.tk_window, padding=5) @@ -400,7 +373,7 @@ class ProgressBarWindow: self.tk_parent.after(0, self.tk_window.destroy) def start(self, total, n=0, divider=1, prefix='', unit='B'): - if (total <= 0) or (n < 0) or (divider <= 0): raise Exception('Invalid arguments!') + if (total <= 0) or (n < 0) or (divider < 1): raise Exception('Invalid arguments!') self.n = n self.total = total @@ -425,16 +398,21 @@ class ProgressBarWindow: self.tk_text_var.set(msg) self.tk_n_var.set(cur_n_div) - self.n = cur_n - if self.withdrawn: + self.tk_window.geometry("+{}+{}".format(self.tk_parent.winfo_x(), self.tk_parent.winfo_y())) self.tk_window.deiconify() - self.setup_taskbar() - self.tk_window.attributes('-topmost', True) - self.tk_window.after(0, lambda: self.tk_window.attributes('-topmost', False)) + self.tk_window.grab_set() + + if g_taskbar: + self.hwnd = int(self.tk_window.wm_frame(), 16) + g_taskbar.ActivateTab(self.hwnd) + g_taskbar.SetProgressState(self.hwnd, g_tlb.TBPF_NORMAL) + self.withdrawn = False if g_taskbar: g_taskbar.SetProgressValue(self.hwnd, cur_n, self.total) + + self.n = cur_n def end(self): self.n = 0 @@ -450,6 +428,8 @@ class ProgressBarWindow: g_taskbar.SetProgressState(self.hwnd, g_tlb.TBPF_NOPROGRESS) g_taskbar.UnregisterTab(self.hwnd) + self.tk_window.grab_release() + self.tk_window.withdraw() self.withdrawn = True @@ -457,14 +437,6 @@ class ProgressBarWindow: def set_prefix(self, prefix): self.prefix = prefix - - def setup_taskbar(self): - if not g_taskbar: return - - self.hwnd = int(self.tk_window.wm_frame(), 16) - - g_taskbar.ActivateTab(self.hwnd) - g_taskbar.SetProgressState(self.hwnd, g_tlb.TBPF_NORMAL) def utilsIsValueAlignedToEndpointPacketSize(value): return bool((value & (g_usbEpMaxPacketSize - 1)) == 0) @@ -500,7 +472,7 @@ def usbGetDeviceEndpoints(): prev_dev = cur_dev = None 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_version = None + usb_version = 0 while True: # Check if the user decided to stop the server. @@ -549,8 +521,6 @@ def usbGetDeviceEndpoints(): break - g_tkCanvas.itemconfigure(g_tkTipMessage, state='normal', text=SERVER_STOP_MSG) - g_Logger.debug('Successfully retrieved USB endpoints! (bus %u, address %u).' % (cur_dev.bus, cur_dev.address)) g_Logger.debug('Max packet size: 0x%X (USB %u.%u).\n' % (g_usbEpMaxPacketSize, usb_version >> 8, (usb_version & 0xFF) >> 4)) @@ -613,12 +583,13 @@ def usbHandleSendFileProperties(cmd_block): filename = raw_filename.decode('utf-8').strip('\x00') # Print info. - info_str = ('File size: 0x%X | Filename length: 0x%X' % (file_size, filename_length)) - if nsp_header_size > 0: info_str += (' | NSP header size: 0x%X' % (nsp_header_size)) - info_str += '.' + dbg_str = ('File size: 0x%X | Filename length: 0x%X' % (file_size, filename_length)) + if nsp_header_size > 0: dbg_str += (' | NSP header size: 0x%X' % (nsp_header_size)) + dbg_str += '.' + g_Logger.debug(dbg_str) - g_Logger.info(info_str) - g_Logger.info('Filename: "%s".' % (filename)) + file_type_str = ('file' if (not g_nspTransferMode) else 'NSP file entry') + g_Logger.info('Receiving %s: "%s".' % (file_type_str, filename)) # Perform integrity checks if (not g_nspTransferMode) and file_size and (nsp_header_size >= file_size): @@ -645,13 +616,6 @@ def usbHandleSendFileProperties(cmd_block): # Perform additional integrity checks and get a file object to work with. if (not g_nspTransferMode) or (g_nspFile is None): - # Check if we're dealing with an absolute path. - if filename[0] == '/': - filename = filename[1:] - - # Replace all slashes with backslashes if we're running under Windows. - if os.name == 'nt': filename = filename.replace('/', '\\') - # Generate full, absolute path to the destination file. fullpath = os.path.abspath(g_outputDir + os.path.sep + filename) @@ -704,8 +668,7 @@ def usbHandleSendFileProperties(cmd_block): usbSendStatus(USB_STATUS_SUCCESS) # Start data transfer stage. - file_type_str = ('file' if (not g_nspTransferMode) else 'NSP file entry') - g_Logger.info('Data transfer started. Saving %s to: "%s".' % (file_type_str, fullpath)) + g_Logger.debug('Data transfer started. Saving %s to: "%s".' % (file_type_str, fullpath)) offset = 0 blksize = USB_TRANSFER_BLOCK_SIZE @@ -717,7 +680,7 @@ def usbHandleSendFileProperties(cmd_block): prefix_filename = (filename[idx+1:] if (idx >= 0) else filename) prefix = ('Current %s: "%s".\n' % (file_type_str, prefix_filename)) - prefix += 'Use your console to cancel the file transfer if you wish to do so.\n\n' + prefix += 'Use your console to cancel the file transfer if you wish to do so.' if (not g_nspTransferMode) or g_nspRemainingSize == (g_nspSize - g_nspHeaderSize): if not g_nspTransferMode: @@ -732,7 +695,7 @@ def usbHandleSendFileProperties(cmd_block): # Get progress bar unit and unit divider. These will be used to display and calculate size values using a specific size unit (B, KiB, MiB, GiB). (unit, unit_divider) = utilsGetSizeUnitAndDivisor(pbar_file_size) - # Initialize progress bar. + # Display progress bar window. g_progressBarWindow.start(pbar_file_size, pbar_n, unit_divider, prefix, unit) else: # Set current prefix (holds the filename for the current NSP file entry). @@ -774,7 +737,8 @@ def usbHandleSendFileProperties(cmd_block): if chunk_size == USB_CMD_HEADER_SIZE: (magic, cmd_id, cmd_block_size) = struct.unpack_from('<4sII', chunk, 0) if (magic == USB_MAGIC_WORD) and (cmd_id == USB_CMD_CANCEL_FILE_TRANSFER): - g_Logger.debug('\nReceived CancelFileTransfer (%02X) command.\n' % (USB_CMD_CANCEL_FILE_TRANSFER)) + g_Logger.debug('\nReceived CancelFileTransfer (%02X) command.' % (USB_CMD_CANCEL_FILE_TRANSFER)) + g_Logger.warning('Transfer cancelled.') # Cancel file transfer. cancelTransfer() @@ -796,7 +760,7 @@ def usbHandleSendFileProperties(cmd_block): if use_pbar: g_progressBarWindow.update(chunk_size) elapsed_time = round(time.time() - start_time) - g_Logger.info('File transfer successfully completed in %s!\n' % (tqdm.format_interval(elapsed_time))) + g_Logger.debug('File transfer successfully completed in %s!\n' % (tqdm.format_interval(elapsed_time))) # Close file handle (if needed). if not g_nspTransferMode: file.close() @@ -857,7 +821,8 @@ def usbCommandHandler(): uiToggleElements(True) return - # Disable server button. + # Update UI. + g_tkCanvas.itemconfigure(g_tkTipMessage, state='normal', text=SERVER_STOP_MSG) g_tkServerButton.configure(state='disabled') # Reset NSP info. @@ -915,7 +880,7 @@ def usbCommandHandler(): if (status is None) or (not usbSendStatus(status)) or (cmd_id == USB_CMD_END_SESSION) or (status == USB_STATUS_UNSUPPORTED_ABI_VERSION): break - g_Logger.info('Stopping server.') + g_Logger.info('\nStopping server.') # Update UI. uiToggleElements(True) @@ -985,29 +950,56 @@ def uiHandleExitProtocolStub(): def uiScaleMeasure(measure): return round(float(measure) * SCALE) -def main(): - global SCALE, g_tkRoot, g_tkCanvas, g_tkDirText, g_tkChooseDirButton, g_tkServerButton, g_tkTipMessage, g_tkScrolledTextLog, g_progressBarWindow +def uiInitialize(): + global SCALE + global g_tkRoot, g_tkCanvas, g_tkDirText, g_tkChooseDirButton, g_tkServerButton, g_tkTipMessage, g_tkScrolledTextLog + global g_tlb, g_taskbar, g_progressBarWindow - # Disable warnings. - warnings.filterwarnings("ignore") - - # Get OS information. - os_type = platform.system() - os_version = platform.version() - - # Check if we're running under Windows Vista or later. + # Enable high DPI scaling under Windows (if possible). dpi_aware = False - win_vista = ((os_type == 'Windows') and (int(os_version[:os_version.find('.')]) >= 6)) - if win_vista: + if g_isWindowsVista: try: - # Enable high DPI scaling. + import ctypes + dpi_aware = (ctypes.windll.user32.SetProcessDPIAware() == 1) if not dpi_aware: dpi_aware = (ctypes.windll.shcore.SetProcessDpiAwareness(1) == 0) except: traceback.print_exc() + # Enable taskbar features under Windows (if possible). + g_tlb = g_taskbar = None + del_tlb = False + + if g_isWindows7: + try: + import comtypes.client as cc + + tlb_fp = open(TASKBAR_LIB_NAME, 'wb') + tlb_fp.write(base64.b64decode(TASKBAR_LIB)) + tlb_fp.close() + del_tlb = True + + g_tlb = cc.GetModule(TASKBAR_LIB_NAME) + + g_taskbar = cc.CreateObject('{56FDF344-FD6D-11D0-958A-006097C9A090}', interface=g_tlb.ITaskbarList3) + g_taskbar.HrInit() + except: + traceback.print_exc() + + if del_tlb: os.remove(TASKBAR_LIB_NAME) + # Create root Tkinter object. g_tkRoot = tk.Tk() + g_tkRoot.title("{} host app v{}".format(USB_DEV_PRODUCT, APP_VERSION)) + g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocol) + g_tkRoot.resizable(False, False) + + # Set window icon. + try: + icon_image = tk.PhotoImage(data=APP_ICON) + g_tkRoot.wm_iconphoto(True, icon_image) + except: + traceback.print_exc() # Get screen resolution. screen_width_px = g_tkRoot.winfo_screenwidth() @@ -1017,29 +1009,7 @@ def main(): screen_dpi = round(g_tkRoot.winfo_fpixels('1i')) # Update scaling factor (if needed). - if win_vista and dpi_aware: SCALE = (float(screen_dpi) / WINDOWS_SCALING_FACTOR) - - # Decode embedded icon and set it. - try: - icon_fp = io.BytesIO(base64.b64decode(APP_ICON)) - icon_image = Image.open(fp=icon_fp, formats=['PNG']) - icon_photo = ImageTk.PhotoImage(icon_image) - - g_tkRoot.wm_iconphoto(True, icon_photo) - - icon_image.close() - icon_fp.close() - except: - traceback.print_exc() - g_tkRoot.withdraw() - messagebox.showerror('Error', 'Unable to decode embedded application icon!', parent=g_tkRoot) - g_tkRoot.destroy() - return - - # Set window properties. - g_tkRoot.resizable(False, False) - g_tkRoot.title("{} host app v{}".format(USB_DEV_PRODUCT, APP_VERSION)) - g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocol) + if g_isWindowsVista and dpi_aware: SCALE = (float(screen_dpi) / WINDOWS_SCALING_FACTOR) # Determine window size. window_width_px = uiScaleMeasure(WINDOW_WIDTH) @@ -1048,7 +1018,7 @@ def main(): # Center window. pos_hor = int((screen_width_px / 2) - (window_width_px / 2)) pos_ver = int((screen_height_px / 2) - (window_height_px / 2)) - g_tkRoot.geometry("+{}+{}".format(pos_hor, pos_ver)) + g_tkRoot.geometry("{}x{}+{}+{}".format(window_width_px, window_height_px, pos_hor, pos_ver)) # Create canvas and fill it with window elements. g_tkCanvas = tk.Canvas(g_tkRoot, width=window_width_px, height=window_height_px) @@ -1079,22 +1049,51 @@ def main(): g_tkCanvas.create_text(uiScaleMeasure(5), uiScaleMeasure(WINDOW_HEIGHT - 10), text="Copyright (c) {}, {}".format(COPYRIGHT_YEAR, USB_DEV_MANUFACTURER), anchor=tk.W) - # Configure logging mechanism. - logging.basicConfig(level=logging.DEBUG) + # Initialize console logger. + console = LogConsole(g_tkScrolledTextLog) + + # Initialize progress bar window object. + bar_format = '{desc}\n\n{percentage:.2f}% - {n:.2f} / {total:.2f} {unit}\nElapsed time: {elapsed}. Remaining time: {remaining}.\nSpeed: {rate_fmt}.' + g_progressBarWindow = ProgressBarWindow(bar_format, g_tkRoot, 'File transfer', False, uiHandleExitProtocolStub) + + # Enter Tkinter main loop. + g_tkRoot.lift() + g_tkRoot.mainloop() + +def main(): + global g_Logger, g_stopEvent, g_osType, g_osVersion, g_isWindows, g_isWindowsVista, g_isWindows7 + + # Disable warnings. + warnings.filterwarnings("ignore") + + # Setup logging mechanism. + logging.basicConfig(level=logging.INFO) + g_Logger = logging.getLogger() if len(g_Logger.handlers): # Remove stderr output handler from logger. log_stderr = g_Logger.handlers[0] g_Logger.removeHandler(log_stderr) - # Initialize console logger. - console = LogConsole(g_tkScrolledTextLog) + # Setup thread event. + g_stopEvent = threading.Event() - # Create hidden progress bar window. - bar_format = '{desc}{percentage:.2f}% - {n:.2f} / {total:.2f} {unit}\nElapsed time: {elapsed}. Remaining time: {remaining}.\nSpeed: {rate_fmt}.' - g_progressBarWindow = ProgressBarWindow(bar_format, g_tkRoot, 'File transfer', False, uiHandleExitProtocolStub) + # Get OS information. + g_osType = platform.system() + g_osVersion = platform.version() - # Enter Tkinter main loop. - g_tkRoot.mainloop() + # Get Windows information. + g_isWindows = (g_osType == 'Windows') + g_isWindowsVista = g_isWindows7 = False + if g_isWindows: + win_ver = g_osVersion.split('.') + win_ver_major = int(win_ver[0]) + win_ver_minor = int(win_ver[1]) + + g_isWindowsVista = (win_ver_major >= 6) + g_isWindows7 = (True if (win_ver_major > 6) else (win_ver_major == 6 and win_ver_minor > 0)) + + # Initialize UI. + uiInitialize() if __name__ == "__main__": try: diff --git a/host/requirements-win32.txt b/host/requirements-win32.txt new file mode 100644 index 0000000..f876833 --- /dev/null +++ b/host/requirements-win32.txt @@ -0,0 +1,2 @@ +-r requirements.txt +comtypes==1.1.9 \ No newline at end of file diff --git a/host/requirements.txt b/host/requirements.txt new file mode 100644 index 0000000..b5b127b --- /dev/null +++ b/host/requirements.txt @@ -0,0 +1,2 @@ +tqdm==4.59.0 +pyusb==1.1.1 \ No newline at end of file diff --git a/usb_abi_specs.txt b/host/usb_abi_specs.txt similarity index 100% rename from usb_abi_specs.txt rename to host/usb_abi_specs.txt diff --git a/host/windows_install_deps.py b/host/windows_install_deps.py new file mode 100644 index 0000000..6bd60bf --- /dev/null +++ b/host/windows_install_deps.py @@ -0,0 +1,14 @@ +# Copyright (c) 2019-2021 Ian Burgwin + +# This is meant to be double-clicked from File Explorer. +# This doesn't import pip as a module in case the way it's executed changes, which it has in the past. +# Instead we call it like we would in the command line. + +from subprocess import run +from os.path import dirname, join +from sys import executable + +root_dir = dirname(__file__) + +run([executable, '-m', 'pip', 'install', '--user', '-r', join(root_dir, 'requirements-win32.txt')]) +input('Press enter to close') diff --git a/host/windows_make_standalone.bat b/host/windows_make_standalone.bat new file mode 100644 index 0000000..0d71d6f --- /dev/null +++ b/host/windows_make_standalone.bat @@ -0,0 +1,10 @@ +mkdir nxdt_host + +cxfreeze nxdt_host.py -s --base-name=Win32GUI --target-dir=nxdt_host --icon=nxdt.ico + +copy ..\README.md nxdt_host +copy ..\LICENSE.md nxdt_host + +python -m zipfile -c nxdt_host.zip nxdt_host + +rmdir /s /q nxdt_host diff --git a/nsul_nxdt_patch.diff b/nsul_nxdt_patch.diff deleted file mode 100644 index 970c89c..0000000 --- a/nsul_nxdt_patch.diff +++ /dev/null @@ -1,513 +0,0 @@ -diff --git a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java -index dd2a1bc..6c8f79e 100644 ---- a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java -+++ b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java -@@ -42,7 +42,6 @@ class NxdtUsbAbi1 { - private final boolean isWindows; - private boolean isWindows10; - -- private static final int NXDT_MAX_DIRECTIVE_SIZE = 0x1000; - private static final int NXDT_FILE_CHUNK_SIZE = 0x800000; - private static final int NXDT_FILE_PROPERTIES_MAX_NAME_LENGTH = 0x300; - -@@ -51,7 +50,9 @@ class NxdtUsbAbi1 { - - private static final int CMD_HANDSHAKE = 0; - private static final int CMD_SEND_FILE_PROPERTIES = 1; -- private static final int CMD_ENDSESSION = 3; -+ private static final int CMD_CANCEL_FILE_TRANSFER = 2; -+ private static final int CMD_SEND_NSP_HEADER = 3; -+ private static final int CMD_ENDSESSION = 4; - - // Standard set of possible replies - private static final byte[] USBSTATUS_SUCCESS = { 0x4e, 0x58, 0x44, 0x54, -@@ -79,9 +80,17 @@ class NxdtUsbAbi1 { - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 }; - -- private short endpointMaxPacketSize; -+ private short endpointMaxPacketSize = 0; - -- private static final int NXDT_USB_TIMEOUT = 5000; -+ private static final int NXDT_USB_CMD_TIMEOUT = 1000; -+ private static final int NXDT_USB_DATA_TIMEOUT = 5000; -+ private static final int USB_BUF_ALIGNMENT = 0x1000; -+ -+ private boolean nspTransferMode = false; -+ private long nspSize = 0; -+ private int nspHeaderSize = 0; -+ private long nspRemainingSize = 0; -+ private File nspFile = null; - - public NxdtUsbAbi1(DeviceHandle handler, - ILogPrinter logPrinter, -@@ -111,6 +120,9 @@ class NxdtUsbAbi1 { - DeviceInformation deviceInformation = DeviceInformation.build(handlerNS); - NsUsbEndpointDescriptor endpointInDescriptor = deviceInformation.getSimplifiedDefaultEndpointDescriptorIn(); - this.endpointMaxPacketSize = endpointInDescriptor.getwMaxPacketSize(); -+ -+ USBSTATUS_SUCCESS[8] = (byte)(this.endpointMaxPacketSize & 0xFF); -+ USBSTATUS_SUCCESS[9] = (byte)((this.endpointMaxPacketSize >> 8) & 0xFF); - } - - private void readLoop(){ -@@ -121,9 +133,7 @@ class NxdtUsbAbi1 { - - while (true){ - directive = readUsbDirective(); -- -- if (isInvalidDirective(directive)) -- continue; -+ if (directive == null || directive.length == 0) continue; - - command = getLEint(directive, 4); - -@@ -134,7 +144,11 @@ class NxdtUsbAbi1 { - case CMD_SEND_FILE_PROPERTIES: - handleSendFileProperties(directive); - break; -+ case CMD_SEND_NSP_HEADER: -+ handleSendNspHeader(directive); -+ break; - case CMD_ENDSESSION: -+ writeUsb(USBSTATUS_SUCCESS); - logPrinter.print("Session successfully ended.", EMsgType.PASS); - return; - default: -@@ -153,28 +167,6 @@ class NxdtUsbAbi1 { - } - } - -- private boolean isInvalidDirective(byte[] message) throws Exception{ -- if (message.length < 0x10){ -- writeUsb(USBSTATUS_MALFORMED_REQUEST); -- logPrinter.print("Directive is too small. Only "+message.length+" bytes received.", EMsgType.FAIL); -- return true; -- } -- -- if (! Arrays.equals(Arrays.copyOfRange(message, 0,4), MAGIC_NXDT)){ -- writeUsb(USBSTATUS_INVALID_MAGIC); -- logPrinter.print("Invalid 'MAGIC'", EMsgType.FAIL); -- return true; -- } -- -- int payloadSize = getLEint(message, 0x8); -- if (payloadSize + 0x10 != message.length){ -- writeUsb(USBSTATUS_MALFORMED_REQUEST); -- logPrinter.print("Invalid directive info block size. "+message.length+" bytes received while "+payloadSize+" expected.", EMsgType.FAIL); -- return true; -- } -- return false; -- } -- - private void performHandshake(byte[] message) throws Exception{ - final byte versionMajor = message[0x10]; - final byte versionMinor = message[0x11]; -@@ -187,30 +179,52 @@ class NxdtUsbAbi1 { - writeUsb(USBSTATUS_UNSUPPORTED_ABI); - throw new Exception("ABI v"+versionABI+" is not supported in current version."); - } -- replyToHandshake(); -- } -- private void replyToHandshake() throws Exception{ -- // Send status response + endpoint max packet size -- ByteBuffer buffer = ByteBuffer.allocate(USBSTATUS_SUCCESS.length + 2).order(ByteOrder.LITTLE_ENDIAN); -- buffer.put(USBSTATUS_SUCCESS); -- buffer.putShort(endpointMaxPacketSize); -- byte[] response = buffer.array(); -- -- writeUsb(response); -+ -+ writeUsb(USBSTATUS_SUCCESS); - } - - private void handleSendFileProperties(byte[] message) throws Exception{ - final long fileSize = getLElong(message, 0x10); - final int fileNameLen = getLEint(message, 0x18); -+ final int headerSize = getLEint(message, 0x1C); - String filename = new String(message, 0x20, fileNameLen, StandardCharsets.UTF_8); - -+ if (!this.nspTransferMode && fileSize > 0 && headerSize >= fileSize) { -+ writeUsb(USBSTATUS_MALFORMED_REQUEST); -+ logPrinter.print("NSP header size non-zero in NSP transfer mode!", EMsgType.FAIL); -+ return; -+ } -+ -+ if (this.nspTransferMode && headerSize > 0) { -+ writeUsb(USBSTATUS_MALFORMED_REQUEST); -+ logPrinter.print("NSP header size non-zero in NSP transfer mode!", EMsgType.FAIL); -+ resetNspInfo(); -+ return; -+ } -+ - if (fileNameLen <= 0 || fileNameLen > NXDT_FILE_PROPERTIES_MAX_NAME_LENGTH){ - writeUsb(USBSTATUS_MALFORMED_REQUEST); - logPrinter.print("Invalid filename length!", EMsgType.FAIL); -+ resetNspInfo(); - return; - } -+ - // TODO: Note, in case of a big amount of small files performace decreses dramatically. It's better to handle this only in case of 1-big-file-transfer -- logPrinter.print("Receiving: '"+filename+"' ("+fileSize+" b)", EMsgType.INFO); -+ if (!this.nspTransferMode) { -+ logPrinter.print("Receiving: '"+filename+"' ("+fileSize+" b)", EMsgType.INFO); -+ } else { -+ logPrinter.print("Receiving NSP file entry: '"+filename+"' ("+fileSize+" b)", EMsgType.INFO); -+ } -+ -+ if (!this.nspTransferMode && fileSize > 0 && headerSize > 0) { -+ // Enable NSP transfer mode -+ this.nspTransferMode = true; -+ this.nspSize = fileSize; -+ this.nspRemainingSize = (fileSize - headerSize); -+ this.nspHeaderSize = headerSize; -+ this.nspFile = null; -+ } -+ - // If RomFs related - if (isRomFs(filename)) { - if (isWindows) -@@ -225,30 +239,105 @@ class NxdtUsbAbi1 { - filename = saveToPath + filename; - } - -- File fileToDump = new File(filename); -- // Check if enough space -- if (fileToDump.getParentFile().getFreeSpace() <= fileSize){ -- writeUsb(USBSTATUS_HOSTIOERROR); -- logPrinter.print("Not enough space on selected volume. Need: "+fileSize+ -- " while available: "+fileToDump.getParentFile().getFreeSpace(), EMsgType.FAIL); -- return; -+ File fileToDump; -+ -+ if (!this.nspTransferMode || (this.nspTransferMode && this.nspFile == null)) { -+ fileToDump = new File(filename); -+ -+ // Check if enough space -+ if (fileToDump.getParentFile().getFreeSpace() <= fileSize){ -+ writeUsb(USBSTATUS_HOSTIOERROR); -+ logPrinter.print("Not enough space on selected volume. Need: "+fileSize+ -+ " while available: "+fileToDump.getParentFile().getFreeSpace(), EMsgType.FAIL); -+ resetNspInfo(); -+ return; -+ } -+ -+ // Check if FS is NOT read-only -+ if (! (fileToDump.canWrite() || fileToDump.createNewFile()) ){ -+ writeUsb(USBSTATUS_HOSTIOERROR); -+ logPrinter.print("Unable to write into selected volume: "+fileToDump.getAbsolutePath(), EMsgType.FAIL); -+ resetNspInfo(); -+ return; -+ } -+ -+ // Delete file if it exists -+ if (fileToDump.exists()) fileToDump.delete(); -+ -+ if (this.nspTransferMode) { -+ // Update NSP file object -+ this.nspFile = fileToDump; -+ -+ // Write padding -+ try (FileOutputStream fos = new FileOutputStream(this.nspFile, false)) { -+ byte[] reserved = new byte[this.nspHeaderSize]; -+ fos.write(reserved); -+ } -+ } -+ } else { -+ fileToDump = this.nspFile; - } -- // Check if FS is NOT read-only -- if (! (fileToDump.canWrite() || fileToDump.createNewFile()) ){ -- writeUsb(USBSTATUS_HOSTIOERROR); -- logPrinter.print("Unable to write into selected volume: "+fileToDump.getAbsolutePath(), EMsgType.FAIL); -+ -+ writeUsb(USBSTATUS_SUCCESS); -+ -+ if (fileSize == 0 || (this.nspTransferMode && fileSize == this.nspSize)) - return; -+ -+ if (dumpFile(fileToDump, fileSize)){ -+ writeUsb(USBSTATUS_SUCCESS); -+ } else { -+ fileToDump.delete(); - } - -+ } -+ -+ private void handleCancelFileTransfer() throws Exception{ -+ resetNspInfo(); - writeUsb(USBSTATUS_SUCCESS); -+ logPrinter.print("User cancelled ongoing file transfer.", EMsgType.FAIL); -+ } - -- if (fileSize == 0) -+ private void handleSendNspHeader(byte[] message) throws Exception{ -+ final int headerSize = getLEint(message, 0x8); -+ -+ if (!this.nspTransferMode) { -+ writeUsb(USBSTATUS_MALFORMED_REQUEST); -+ logPrinter.print("Received NSP send header request outside of NSP transfer mode!", EMsgType.FAIL); -+ resetNspInfo(); - return; -+ } - -- dumpFile(fileToDump, fileSize); -+ if (this.nspRemainingSize > 0) { -+ writeUsb(USBSTATUS_MALFORMED_REQUEST); -+ logPrinter.print("Received NSP send header request without receiving all NSP file entry data!", EMsgType.FAIL); -+ resetNspInfo(); -+ return; -+ } -+ -+ if (headerSize != this.nspHeaderSize) { -+ writeUsb(USBSTATUS_MALFORMED_REQUEST); -+ logPrinter.print("Received NSP header size mismatch! "+headerSize+" != "+this.nspHeaderSize, EMsgType.FAIL); -+ resetNspInfo(); -+ return; -+ } -+ -+ try (RandomAccessFile raf = new RandomAccessFile(this.nspFile, "rw")) { -+ byte[] headerData = Arrays.copyOfRange(message, 0x10, headerSize + 0x10); -+ raf.seek(0); -+ raf.write(headerData); -+ } -+ -+ resetNspInfo(); - - writeUsb(USBSTATUS_SUCCESS); -+ } - -+ private void resetNspInfo(){ -+ this.nspTransferMode = false; -+ this.nspSize = 0; -+ this.nspHeaderSize = 0; -+ this.nspRemainingSize = 0; -+ this.nspFile = null; - } - - private int getLEint(byte[] bytes, int fromOffset){ -@@ -277,9 +366,9 @@ class NxdtUsbAbi1 { - throw new Exception("Unable to create dir(s) for file in "+folderForTheFile); - } - -- // @see https://bugs.openjdk.java.net/browse/JDK-8146538 -- private void dumpFile(File file, long size) throws Exception{ -+ private boolean dumpFile(File file, long size) throws Exception{ - FileOutputStream fos = new FileOutputStream(file, true); -+ boolean success = true; - - try (BufferedOutputStream bos = new BufferedOutputStream(fos)) { - FileDescriptor fd = fos.getFD(); -@@ -287,31 +376,44 @@ class NxdtUsbAbi1 { - long received = 0; - int bufferSize; - -- while (received+NXDT_FILE_CHUNK_SIZE < size) { -- //readBuffer = readUsbFile(); -- readBuffer = readUsbFileDebug(NXDT_FILE_CHUNK_SIZE); -+ while((received + NXDT_FILE_CHUNK_SIZE) < size) { -+ readBuffer = readUsb(NXDT_FILE_CHUNK_SIZE, NXDT_USB_DATA_TIMEOUT); - bos.write(readBuffer); - if (isWindows10) - fd.sync(); - bufferSize = readBuffer.length; - received += bufferSize; - -- logPrinter.updateProgress((double)received / (double)size); -+ if (bufferSize == 0x10 && Arrays.equals(Arrays.copyOfRange(readBuffer, 0, 4), MAGIC_NXDT)) { -+ int cmd = getLEint(readBuffer, 4); -+ if (cmd == CMD_CANCEL_FILE_TRANSFER){ -+ handleCancelFileTransfer(); -+ success = false; -+ break; -+ } -+ } -+ -+ if (!this.nspTransferMode) { -+ logPrinter.updateProgress((double)received / (double)size); -+ } else { -+ this.nspRemainingSize -= bufferSize; -+ logPrinter.updateProgress((double)(this.nspSize - this.nspRemainingSize) / (double)this.nspSize); -+ } -+ } -+ if (success){ -+ int lastChunkSize = (int)((size - received) + 1); -+ readBuffer = readUsb(lastChunkSize, NXDT_USB_DATA_TIMEOUT); -+ bos.write(readBuffer); -+ if (isWindows10) -+ fd.sync(); -+ this.nspRemainingSize -= lastChunkSize; - } -- int lastChunkSize = (int)(size - received) + 1; -- readBuffer = readUsbFileDebug(lastChunkSize); -- bos.write(readBuffer); -- if (isWindows10) -- fd.sync(); - } finally { -- logPrinter.updateProgress(1.0); -+ if (success && (!this.nspTransferMode || (this.nspTransferMode && this.nspRemainingSize == 0))) logPrinter.updateProgress(1.0); - } -+ -+ return success; - } -- /* Handle Zero-length terminator -- private boolean isAligned(long size){ -- return ((size & (endpointMaxPacketSize - 1)) == 0); -- } -- */ - - /** Sending any byte array to USB device **/ - private void writeUsb(byte[] message) throws Exception{ -@@ -322,7 +424,7 @@ class NxdtUsbAbi1 { - if ( parent.isCancelled() ) - throw new InterruptedException("Execution interrupted"); - -- int result = LibUsb.bulkTransfer(handlerNS, (byte) 0x01, writeBuffer, writeBufTransferred, NXDT_USB_TIMEOUT); -+ int result = LibUsb.bulkTransfer(handlerNS, (byte) 0x01, writeBuffer, writeBufTransferred, NXDT_USB_CMD_TIMEOUT); - - if (result == LibUsb.SUCCESS) { - if (writeBufTransferred.get() == message.length) -@@ -335,47 +437,61 @@ class NxdtUsbAbi1 { - "\n Returned: " + UsbErrorCodes.getErrCode(result) + - "\n (execution stopped)"); - } -+ - /** -- * Reading what USB device responded (command). -+ * Reads an USB directive. - * @return byte array if data read successful - * 'null' if read failed - * */ - private byte[] readUsbDirective() throws Exception{ -- ByteBuffer readBuffer = ByteBuffer.allocateDirect(NXDT_MAX_DIRECTIVE_SIZE); -- // We can limit it to 32 bytes, but there is a non-zero chance to got OVERFLOW from libusb. -- IntBuffer readBufTransferred = IntBuffer.allocate(1); -- int result; -- while (! parent.isCancelled()) { -- result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81 -+ byte[] cmd_header = null, payload = null, directive = null; -+ int payloadSize = 0; - -- switch (result) { -- case LibUsb.SUCCESS: -- int trans = readBufTransferred.get(); -- byte[] receivedBytes = new byte[trans]; -- readBuffer.get(receivedBytes); -- return receivedBytes; -- case LibUsb.ERROR_TIMEOUT: -- break; -- default: -- throw new Exception("Data transfer issue [read command]" + -- "\n Returned: " + UsbErrorCodes.getErrCode(result)+ -- "\n (execution stopped)"); -+ cmd_header = readUsb(0x10, NXDT_USB_CMD_TIMEOUT); -+ if (cmd_header == null || cmd_header.length == 0) return null; -+ -+ if (cmd_header.length != 0x10){ -+ writeUsb(USBSTATUS_MALFORMED_REQUEST); -+ logPrinter.print("Command header is too small. Only "+cmd_header.length+" bytes received.", EMsgType.FAIL); -+ return null; -+ } -+ -+ if (! Arrays.equals(Arrays.copyOfRange(cmd_header, 0, 4), MAGIC_NXDT)){ -+ writeUsb(USBSTATUS_INVALID_MAGIC); -+ logPrinter.print("Invalid 'MAGIC'", EMsgType.FAIL); -+ return null; -+ } -+ -+ payloadSize = getLEint(cmd_header, 8); -+ if (payloadSize > 0){ -+ payload = readUsb(payloadSize + 1, NXDT_USB_CMD_TIMEOUT); -+ if (payload == null || payload.length != payloadSize){ -+ writeUsb(USBSTATUS_MALFORMED_REQUEST); -+ logPrinter.print("Command payload size mismatch. Received "+payload.length+" bytes.", EMsgType.FAIL); -+ return null; - } - } -- throw new InterruptedException(); -+ -+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); -+ outputStream.write(cmd_header); -+ if (payloadSize > 0) outputStream.write(payload); -+ directive = outputStream.toByteArray(); -+ -+ return directive; - } -+ - /** -- * Reading what USB device responded (file). -+ * Reading what USB device responded (command). - * @return byte array if data read successful - * 'null' if read failed - * */ -- private byte[] readUsbFile() throws Exception{ -- ByteBuffer readBuffer = ByteBuffer.allocateDirect(NXDT_FILE_CHUNK_SIZE); -+ private byte[] readUsb(int length, int timeout) throws Exception{ -+ ByteBuffer readBuffer = ByteBuffer.allocateDirect(alignUp(length, USB_BUF_ALIGNMENT)); - IntBuffer readBufTransferred = IntBuffer.allocate(1); - int result; -- int countDown = 0; -- while (! parent.isCancelled() && countDown < 5) { -- result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 1000); -+ -+ while (! parent.isCancelled()) { -+ result = LibUsb.bulkTransfer(handlerNS, (byte)0x81, readBuffer, readBufTransferred, timeout); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81 - - switch (result) { - case LibUsb.SUCCESS: -@@ -384,33 +500,17 @@ class NxdtUsbAbi1 { - readBuffer.get(receivedBytes); - return receivedBytes; - case LibUsb.ERROR_TIMEOUT: -- countDown++; - break; - default: -- throw new Exception("Data transfer issue [read file]" + -+ throw new Exception("Data transfer issue [read]" + - "\n Returned: " + UsbErrorCodes.getErrCode(result)+ - "\n (execution stopped)"); - } - } - throw new InterruptedException(); - } -- -- private byte[] readUsbFileDebug(int chunkSize) throws Exception { -- ByteBuffer readBuffer = ByteBuffer.allocateDirect(chunkSize); -- IntBuffer readBufTransferred = IntBuffer.allocate(1); -- if (parent.isCancelled()) -- throw new InterruptedException(); -- -- int result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, NXDT_USB_TIMEOUT); - -- if (result == LibUsb.SUCCESS) { -- int trans = readBufTransferred.get(); -- byte[] receivedBytes = new byte[trans]; -- readBuffer.get(receivedBytes); -- return receivedBytes; -- } -- throw new Exception("Data transfer issue [read file]" + -- "\n Returned: " + UsbErrorCodes.getErrCode(result) + -- "\n (execution stopped)"); -+ private int alignUp(int value, int alignment){ -+ return ((value + (alignment - 1)) & ~(alignment - 1)); - } - } -diff --git a/src/main/resources/NSLMain.fxml b/src/main/resources/NSLMain.fxml -index a2d42d6..9114c3d 100644 ---- a/src/main/resources/NSLMain.fxml -+++ b/src/main/resources/NSLMain.fxml -@@ -71,12 +71,12 @@ Steps to roll NXDT functionality back: - - - -- -+ - - - - -- -+ - - -