mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-26 04:02:11 +00:00
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.
This commit is contained in:
parent
491b70d715
commit
a016ba92db
9 changed files with 157 additions and 644 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -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
|
||||
|
|
BIN
host/nxdt.ico
Normal file
BIN
host/nxdt.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
* nxdt_host.pyw
|
||||
* nxdt_host.py
|
||||
*
|
||||
* Copyright (c) 2021, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
|
@ -20,20 +20,21 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
# 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,17 +398,22 @@ 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
|
||||
self.total = 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
|
||||
|
||||
|
@ -458,14 +438,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:
|
2
host/requirements-win32.txt
Normal file
2
host/requirements-win32.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
-r requirements.txt
|
||||
comtypes==1.1.9
|
2
host/requirements.txt
Normal file
2
host/requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
tqdm==4.59.0
|
||||
pyusb==1.1.1
|
14
host/windows_install_deps.py
Normal file
14
host/windows_install_deps.py
Normal file
|
@ -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')
|
10
host/windows_make_standalone.bat
Normal file
10
host/windows_make_standalone.bat
Normal file
|
@ -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
|
|
@ -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:
|
||||
<SVGPath content="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M10,16V19.08L13.08,16H20V4H4V16H10M17,11H15V9H17V11M13,11H11V9H13V11M9,11H7V9H9V11Z" />
|
||||
</graphic>
|
||||
</Tab>
|
||||
- <Tab closable="false" disable="true">
|
||||
+ <Tab closable="false" disable="false">
|
||||
<content>
|
||||
<fx:include fx:id="NXDTab" source="NXDTab.fxml" VBox.vgrow="ALWAYS" />
|
||||
</content>
|
||||
<graphic>
|
||||
- <SVGPath content="M 7 0 L 0 4 C -0.02484618 7.6613523 4.6259293e-18 7.3229335 0 10.984375 C 0 10.993031 0.015625 7 0.015625 8 L 13 0 L 7 0 z M 17.966797 6.46875 L 17.966797 10.673828 C 17.715715 10.211268 17.396999 9.8685131 17.011719 9.6464844 C 16.630766 9.4198301 16.171566 9.3066406 15.634766 9.3066406 C 14.755976 9.3066406 14.040441 9.682293 13.486328 10.431641 C 12.936542 11.180987 12.660156 12.165561 12.660156 13.386719 C 12.660156 14.607877 12.936542 15.59245 13.486328 16.341797 C 14.040441 17.091144 14.755976 17.466797 15.634766 17.466797 C 16.171566 17.466797 16.630766 17.354841 17.011719 17.132812 C 17.396999 16.90616 17.715715 16.562169 17.966797 16.099609 L 17.966797 17.265625 L 19.160156 17.265625 L 19.160156 6.46875 L 17.966797 6.46875 z M 20.572266 7.3652344 L 20.572266 9.5546875 L 19.800781 9.5546875 L 19.800781 10.539062 L 20.572266 10.539062 L 20.572266 14.724609 C 20.572266 15.688531 20.728201 16.353495 21.037109 16.720703 C 21.346016 17.083321 21.906432 17.265625 22.71875 17.265625 L 23.800781 17.265625 L 23.800781 16.205078 L 22.71875 16.205078 C 22.280176 16.205078 21.98867 16.114559 21.84375 15.935547 C 21.702645 15.756534 21.630859 15.353453 21.630859 14.724609 L 21.630859 10.539062 L 23.800781 10.539062 L 23.800781 9.5546875 L 21.630859 9.5546875 L 21.630859 7.3652344 L 20.572266 7.3652344 z M 3.6386719 9.3066406 C 3.1397071 9.3066406 2.6982722 9.4230165 2.3144531 9.6542969 C 1.9348986 9.8855772 1.6037331 10.233986 1.3222656 10.701172 L 1.3222656 9.4941406 L 0.13867188 9.4941406 L 0.13867188 17.265625 L 1.3222656 17.265625 L 1.3222656 12.873047 C 1.3222656 12.114448 1.5062865 11.515605 1.8730469 11.076172 C 2.2398077 10.636739 2.7395664 10.417969 3.375 10.417969 C 3.9038175 10.417969 4.3000442 10.599421 4.5644531 10.964844 C 4.8288618 11.330266 4.9609375 11.881715 4.9609375 12.617188 L 4.9609375 17.265625 L 6.1386719 17.265625 L 6.1386719 12.576172 C 6.1386719 11.503031 5.9280604 10.691072 5.5058594 10.140625 C 5.0836585 9.5855522 4.4617505 9.3066406 3.6386719 9.3066406 z M 6.984375 9.4941406 L 9.2207031 13.199219 L 6.7773438 17.265625 L 7.9960938 17.265625 L 9.828125 14.212891 L 11.658203 17.265625 L 12.878906 17.265625 L 10.484375 13.275391 L 12.759766 9.4941406 L 11.541016 9.4941406 L 9.8730469 12.263672 L 8.2050781 9.4941406 L 6.984375 9.4941406 z M 15.927734 10.375 C 16.559769 10.375 17.056283 10.643118 17.419922 11.179688 C 17.783557 11.711631 17.966797 12.447722 17.966797 13.386719 C 17.966797 14.325715 17.783557 15.06304 17.419922 15.599609 C 17.056283 16.131553 16.559769 16.398437 15.927734 16.398438 C 15.295699 16.398438 14.797233 16.131553 14.433594 15.599609 C 14.074286 15.06304 13.894531 14.325715 13.894531 13.386719 C 13.894531 12.447722 14.074286 11.711631 14.433594 11.179688 C 14.797233 10.643118 15.295699 10.375 15.927734 10.375 z M 24 18.400391 C 24.0056 21.386719 23.984375 22 23.984375 19 L 15 23.982422 L 23.984375 23.996094 C 23.993075 23.996094 24 23.990492 24 23.982422 L 24 18.400391 z" visible="false" />
|
||||
+ <SVGPath content="M 7 0 L 0 4 C -0.02484618 7.6613523 4.6259293e-18 7.3229335 0 10.984375 C 0 10.993031 0.015625 7 0.015625 8 L 13 0 L 7 0 z M 17.966797 6.46875 L 17.966797 10.673828 C 17.715715 10.211268 17.396999 9.8685131 17.011719 9.6464844 C 16.630766 9.4198301 16.171566 9.3066406 15.634766 9.3066406 C 14.755976 9.3066406 14.040441 9.682293 13.486328 10.431641 C 12.936542 11.180987 12.660156 12.165561 12.660156 13.386719 C 12.660156 14.607877 12.936542 15.59245 13.486328 16.341797 C 14.040441 17.091144 14.755976 17.466797 15.634766 17.466797 C 16.171566 17.466797 16.630766 17.354841 17.011719 17.132812 C 17.396999 16.90616 17.715715 16.562169 17.966797 16.099609 L 17.966797 17.265625 L 19.160156 17.265625 L 19.160156 6.46875 L 17.966797 6.46875 z M 20.572266 7.3652344 L 20.572266 9.5546875 L 19.800781 9.5546875 L 19.800781 10.539062 L 20.572266 10.539062 L 20.572266 14.724609 C 20.572266 15.688531 20.728201 16.353495 21.037109 16.720703 C 21.346016 17.083321 21.906432 17.265625 22.71875 17.265625 L 23.800781 17.265625 L 23.800781 16.205078 L 22.71875 16.205078 C 22.280176 16.205078 21.98867 16.114559 21.84375 15.935547 C 21.702645 15.756534 21.630859 15.353453 21.630859 14.724609 L 21.630859 10.539062 L 23.800781 10.539062 L 23.800781 9.5546875 L 21.630859 9.5546875 L 21.630859 7.3652344 L 20.572266 7.3652344 z M 3.6386719 9.3066406 C 3.1397071 9.3066406 2.6982722 9.4230165 2.3144531 9.6542969 C 1.9348986 9.8855772 1.6037331 10.233986 1.3222656 10.701172 L 1.3222656 9.4941406 L 0.13867188 9.4941406 L 0.13867188 17.265625 L 1.3222656 17.265625 L 1.3222656 12.873047 C 1.3222656 12.114448 1.5062865 11.515605 1.8730469 11.076172 C 2.2398077 10.636739 2.7395664 10.417969 3.375 10.417969 C 3.9038175 10.417969 4.3000442 10.599421 4.5644531 10.964844 C 4.8288618 11.330266 4.9609375 11.881715 4.9609375 12.617188 L 4.9609375 17.265625 L 6.1386719 17.265625 L 6.1386719 12.576172 C 6.1386719 11.503031 5.9280604 10.691072 5.5058594 10.140625 C 5.0836585 9.5855522 4.4617505 9.3066406 3.6386719 9.3066406 z M 6.984375 9.4941406 L 9.2207031 13.199219 L 6.7773438 17.265625 L 7.9960938 17.265625 L 9.828125 14.212891 L 11.658203 17.265625 L 12.878906 17.265625 L 10.484375 13.275391 L 12.759766 9.4941406 L 11.541016 9.4941406 L 9.8730469 12.263672 L 8.2050781 9.4941406 L 6.984375 9.4941406 z M 15.927734 10.375 C 16.559769 10.375 17.056283 10.643118 17.419922 11.179688 C 17.783557 11.711631 17.966797 12.447722 17.966797 13.386719 C 17.966797 14.325715 17.783557 15.06304 17.419922 15.599609 C 17.056283 16.131553 16.559769 16.398437 15.927734 16.398438 C 15.295699 16.398438 14.797233 16.131553 14.433594 15.599609 C 14.074286 15.06304 13.894531 14.325715 13.894531 13.386719 C 13.894531 12.447722 14.074286 11.711631 14.433594 11.179688 C 14.797233 10.643118 15.295699 10.375 15.927734 10.375 z M 24 18.400391 C 24.0056 21.386719 23.984375 22 23.984375 19 L 15 23.982422 L 23.984375 23.996094 C 23.993075 23.996094 24 23.990492 24 23.982422 L 24 18.400391 z" visible="true" />
|
||||
</graphic>
|
||||
</Tab>
|
||||
</tabs>
|
Loading…
Reference in a new issue