From bd1b56e5c2c548a2239a90b086f289c25970db4b Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Sun, 26 Nov 2023 22:10:42 +0100 Subject: [PATCH] host: switch to Nuitka-based EXE building. Other changes include: * host: rename windows_install_deps.py -> install_deps.py and update it to support multiple operating systems. * host: restore assertions and fix most static analysis errors related to type aliases and checks. * host: catch exceptions thrown by usb.core.find() in usbGetDeviceEndpoints(). * host: move code to unconditionally enable 32-bit paths under Windows to uiStartServer() and cliInitialize(), respectively. * host: remove incomplete file if the ongoing transfer was cancelled and we're not dealing with a NSP file. --- .gitignore | 7 +- ...indows_install_deps.py => install_deps.py} | 5 +- host/nxdt_host.py | 206 ++++++++++-------- host/windows_make_standalone.bat | 18 +- 4 files changed, 126 insertions(+), 110 deletions(-) rename host/{windows_install_deps.py => install_deps.py} (66%) diff --git a/.gitignore b/.gitignore index 29aa6c1..1e15bce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ .vscode build host/__pycache__ -host/installer -host/build -host/dist +host/standalone +host/nxdt_host.build +host/nxdt_host.dist +host/nxdt_host.onefile-build host/nxdumptool *.elf *.nacp diff --git a/host/windows_install_deps.py b/host/install_deps.py similarity index 66% rename from host/windows_install_deps.py rename to host/install_deps.py index 38a7ba7..12e9d59 100644 --- a/host/windows_install_deps.py +++ b/host/install_deps.py @@ -7,8 +7,11 @@ from subprocess import run from os.path import dirname, join from sys import executable +from platform import system root_dir = dirname(__file__) -run([executable, '-m', 'pip', 'install', '-r', join(root_dir, 'requirements-win32.txt')]) +requirements_file = ('requirements-win32.txt' if system() == 'Windows' else 'requirements.txt') + +run([executable, '-m', 'pip', 'install', '-r', join(root_dir, requirements_file)]) input('Press enter to close') diff --git a/host/nxdt_host.py b/host/nxdt_host.py index 11c6dc5..f838262 100644 --- a/host/nxdt_host.py +++ b/host/nxdt_host.py @@ -58,7 +58,7 @@ from tqdm import tqdm from argparse import ArgumentParser from io import BufferedWriter -from typing import List, Tuple, Any, Callable, Optional +from typing import Generator, Any, Callable # Scaling factors. WINDOWS_SCALING_FACTOR = 96.0 @@ -137,8 +137,8 @@ SERVER_START_MSG = f'Please connect a Nintendo Switch console running {USB_DEV_P SERVER_STOP_MSG = f'Exit {USB_DEV_PRODUCT} on your console or disconnect it at any time to stop the server.' # Default directory paths. -INITIAL_DIR = os.path.abspath(os.path.dirname(sys.executable if getattr(sys, 'frozen', False) else __file__)) -DEFAULT_DIR = (INITIAL_DIR + os.path.sep + USB_DEV_PRODUCT) +INITIAL_DIR = os.path.dirname(os.path.abspath(os.path.expanduser(os.path.expandvars(sys.argv[0])))) +DEFAULT_DIR = os.path.join(INITIAL_DIR, USB_DEV_PRODUCT) # Application icon (PNG). # Embedded to load it as the icon for all windows using PhotoImage (which doesn't support ICO files) + wm_iconphoto. @@ -225,7 +225,7 @@ APP_ICON = b'iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAAR b'43EDnoiNHI8a8FRs5HjMgCdjI8cj7+rp2MhR/Z3p7b5gyzRyjN0ei80cwP+bQrjkWSh1LgAAAABJRU5ErkJggg==' # Taskbar Type Library (TLB). Used under Windows 7 or greater. -TASKBAR_LIB_PATH = (INITIAL_DIR + os.path.sep + 'TaskbarLib.tlb') +TASKBAR_LIB_PATH = os.path.join(INITIAL_DIR, 'TaskbarLib.tlb') TASKBAR_LIB = b'TVNGVAIAAQAAAAAACQQAAAAAAABBAAAAAQAAAAAAAAAOAAAA/////wAAAAAAAAAATgAAADMDAAAAAAAA/////xgAAAAgAAAAgAAAAP////8AAAAAAAAAAGQAAADIAAAA' + \ b'LAEAAJABAAD0AQAAWAIAALwCAAAgAwAAhAMAAOgDAABMBAAAsAQAABQFAAB8AQAAeAUAAP////8PAAAA/////wAAAAD/////DwAAAP////8AAAAA/////w8AAABMCAAA' + \ @@ -303,7 +303,7 @@ TASKBAR_LIB = b'TVNGVAIAAQAAAAAACQQAAAAAAABBAAAAAQAAAAAAAAAOAAAA/////wAAAAAAAAAA # Global variables used throughout the code. g_cliMode: bool = False g_outputDir: str = '' -g_logLevelIntVar: Optional[tk.IntVar] = None +g_logLevelIntVar: tk.IntVar | None = None g_osType: str = '' g_osVersion: str = '' @@ -312,18 +312,18 @@ g_isWindows: bool = False g_isWindowsVista: bool = False g_isWindows7: bool = False -g_tkRoot: Optional[tk.Tk] = None -g_tkCanvas: Optional[tk.Canvas] = None -g_tkDirText: Optional[tk.Text] = None -g_tkChooseDirButton: Optional[tk.Button] = None -g_tkServerButton: Optional[tk.Button] = None -g_tkTipMessage: Any = None -g_tkScrolledTextLog: Optional[scrolledtext.ScrolledText] = None -g_tkVerboseCheckbox: Optional[tk.Checkbutton] = None +g_tkRoot: tk.Tk | None = None +g_tkCanvas: tk.Canvas | None = None +g_tkDirText: tk.Text | None = None +g_tkChooseDirButton: tk.Button | None = None +g_tkServerButton: tk.Button | None = None +g_tkTipMessage: int = 0 +g_tkScrolledTextLog: scrolledtext.ScrolledText | None = None +g_tkVerboseCheckbox: tk.Checkbutton | None = None -g_logger: Optional[logging.Logger] = None +g_logger: logging.Logger | None = None -g_stopEvent: Optional[threading.Event] = None +g_stopEvent: threading.Event | None = None g_tlb: Any = None g_taskbar: Any = None @@ -343,12 +343,12 @@ g_nspTransferMode: bool = False g_nspSize: int = 0 g_nspHeaderSize: int = 0 g_nspRemainingSize: int = 0 -g_nspFile: Optional[BufferedWriter] = None +g_nspFile: BufferedWriter | None = None g_nspFilePath: str = '' # Reference: https://beenje.github.io/blog/posts/logging-to-a-tkinter-scrolledtext-widget. class LogQueueHandler(logging.Handler): - def __init__(self, log_queue: queue.Queue): + def __init__(self, log_queue: queue.Queue) -> None: super().__init__() self.log_queue = log_queue @@ -361,8 +361,8 @@ class LogQueueHandler(logging.Handler): # Reference: https://beenje.github.io/blog/posts/logging-to-a-tkinter-scrolledtext-widget. class LogConsole: - def __init__(self, scrolled_text: Optional[scrolledtext.ScrolledText] = None): - #assert g_logger is not None + def __init__(self, scrolled_text: scrolledtext.ScrolledText | None = None) -> None: + assert g_logger is not None self.scrolled_text = scrolled_text self.frame = (self.scrolled_text.winfo_toplevel() if self.scrolled_text else None) @@ -406,7 +406,7 @@ class LogConsole: class ProgressBarWindow: global g_tlb, g_taskbar - def __init__(self, bar_format: str = '', tk_parent: Any = None, window_title: str = '', window_resize: bool = False, window_protocol: Optional[Callable] = None): + def __init__(self, bar_format: str = '', tk_parent: Any = None, window_title: str = '', window_resize: bool = False, window_protocol: Callable | None = None) -> None: self.n: int = 0 self.total: int = 0 self.divider: float = 1.0 @@ -421,11 +421,11 @@ class ProgressBarWindow: self.tk_parent = tk_parent self.tk_window = (tk.Toplevel(self.tk_parent) if self.tk_parent else None) self.withdrawn = False - self.tk_text_var: Optional[tk.StringVar] = None - self.tk_n_var: Optional[tk.DoubleVar] = None - self.tk_pbar: Optional[ttk.Progressbar] = None + self.tk_text_var: tk.StringVar | None = None + self.tk_n_var: tk.DoubleVar | None = None + self.tk_pbar: ttk.Progressbar | None = None - self.pbar: Optional[tqdm] = None + self.pbar: tqdm | None = None if self.tk_window: self.tk_window.withdraw() @@ -451,8 +451,8 @@ class ProgressBarWindow: self.tk_pbar.configure(maximum=100, mode='indeterminate') self.tk_pbar.pack() - def __del__(self): - if self.tk_parent: + def __del__(self) -> None: + if self.tk_window: self.tk_parent.after(0, self.tk_window.destroy) def start(self, total: int, n: int = 0, divider: int = 1, prefix: str = '', unit: str = 'B') -> None: @@ -479,8 +479,8 @@ class ProgressBarWindow: return if self.tk_window: - #assert self.tk_text_var is not None - #assert self.tk_n_var is not None + assert self.tk_text_var is not None + assert self.tk_n_var is not None cur_n_div = (float(cur_n) / self.divider) self.elapsed_time = (time.time() - self.start_time) @@ -505,7 +505,7 @@ class ProgressBarWindow: if g_taskbar: g_taskbar.SetProgressValue(self.hwnd, cur_n, self.total) else: - #assert self.pbar is not None + assert self.pbar is not None n_div = (float(n) / self.divider) self.pbar.update(n_div) @@ -522,7 +522,7 @@ class ProgressBarWindow: self.elapsed_time = 0 if self.tk_window: - #assert self.tk_pbar is not None + assert self.tk_pbar is not None if g_taskbar: g_taskbar.SetProgressState(self.hwnd, g_tlb.TBPF_NOPROGRESS) @@ -535,7 +535,7 @@ class ProgressBarWindow: self.tk_pbar.configure(maximum=100, mode='indeterminate') else: - #assert self.pbar is not None + assert self.pbar is not None self.pbar.close() self.pbar = None print() @@ -543,7 +543,7 @@ class ProgressBarWindow: def set_prefix(self, prefix) -> None: self.prefix = prefix -g_progressBarWindow: Optional[ProgressBarWindow] = None +g_progressBarWindow: ProgressBarWindow | None = None def eprint(*args, **kwargs) -> None: print(*args, file=sys.stderr, **kwargs) @@ -586,7 +586,7 @@ def utilsResetNspInfo(delete: bool = False) -> None: g_nspFile = None g_nspFilePath = '' -def utilsGetSizeUnitAndDivisor(size: int) -> Tuple[str, int]: +def utilsGetSizeUnitAndDivisor(size: int) -> tuple[str, int]: size_suffixes = [ 'B', 'KiB', 'MiB', 'GiB' ] size_suffixes_count = len(size_suffixes) @@ -603,10 +603,11 @@ def utilsGetSizeUnitAndDivisor(size: int) -> Tuple[str, int]: def usbGetDeviceEndpoints() -> bool: global g_usbEpIn, g_usbEpOut, g_usbEpMaxPacketSize - #assert g_logger is not None - #assert g_stopEvent is not None + assert g_logger is not None + assert g_stopEvent is not None - prev_dev = cur_dev = None + cur_dev: Generator[usb.core.Device, Any, None] | 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_out_lambda = lambda ep: usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_OUT usb_version = 0 @@ -622,8 +623,20 @@ def usbGetDeviceEndpoints() -> bool: # Find a connected USB device with a matching VID/PID pair. # Using == here to compare both device instances would also compare the backend, so we'll just compare certain elements manually. - cur_dev = usb.core.find(idVendor=USB_DEV_VID, idProduct=USB_DEV_PID) - if (cur_dev is None) or ((prev_dev is not None) and (cur_dev.bus == prev_dev.bus) and (cur_dev.address == prev_dev.address)): + try: + cur_dev = usb.core.find(find_all=False, idVendor=USB_DEV_VID, idProduct=USB_DEV_PID) + except: + if not g_cliMode: + utilsLogException(traceback.format_exc()) + + g_logger.error('\nFatal error ocurred while enumerating USB devices.') + + if g_isWindows: + g_logger.error('\nTry reinstalling the libusbK driver using Zadig.') + + return False + + if (not isinstance(cur_dev, usb.core.Device)) or (isinstance(prev_dev, usb.core.Device) and (cur_dev.bus == prev_dev.bus) and (cur_dev.address == prev_dev.address)): time.sleep(0.1) continue @@ -672,8 +685,6 @@ def usbGetDeviceEndpoints() -> bool: return True def usbRead(size: int, timeout: int = -1) -> bytes: - #assert g_logger is not None - rd = b'' try: @@ -682,13 +693,13 @@ def usbRead(size: int, timeout: int = -1) -> bytes: except usb.core.USBError: if not g_cliMode: utilsLogException(traceback.format_exc()) - g_logger.error('\nUSB timeout triggered or console disconnected.') + + if g_logger is not None: + g_logger.error('\nUSB timeout triggered or console disconnected.') return rd def usbWrite(data: bytes, timeout: int = -1) -> int: - #assert g_logger is not None - wr = 0 try: @@ -696,18 +707,20 @@ def usbWrite(data: bytes, timeout: int = -1) -> int: except usb.core.USBError: if not g_cliMode: utilsLogException(traceback.format_exc()) - g_logger.error('\nUSB timeout triggered or console disconnected.') + + if g_logger is not None: + g_logger.error('\nUSB timeout triggered or console disconnected.') return wr def usbSendStatus(code: int) -> bool: - status = struct.pack('<4sIH6p', USB_MAGIC_WORD, code, g_usbEpMaxPacketSize, b'') + status = struct.pack('<4sIH6x', USB_MAGIC_WORD, code, g_usbEpMaxPacketSize) return bool(usbWrite(status, USB_TRANSFER_TIMEOUT) == len(status)) def usbHandleStartSession(cmd_block: bytes) -> int: global g_nxdtVersionMajor, g_nxdtVersionMinor, g_nxdtVersionMicro, g_nxdtAbiVersionMajor, g_nxdtAbiVersionMinor, g_nxdtGitCommit - #assert g_logger is not None + assert g_logger is not None if g_cliMode: print() @@ -736,8 +749,8 @@ def usbHandleStartSession(cmd_block: bytes) -> int: def usbHandleSendFileProperties(cmd_block: bytes) -> int | None: global g_nspTransferMode, g_nspSize, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath, g_outputDir, g_tkRoot, g_progressBarWindow - #assert g_logger is not None - #assert g_progressBarWindow is not None + assert g_logger is not None + assert g_progressBarWindow is not None if g_cliMode and not g_nspTransferMode: print() @@ -786,10 +799,7 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None: if (not g_nspTransferMode) or (g_nspFile is None): # Generate full, absolute path to the destination file. fullpath = os.path.abspath(g_outputDir + os.path.sep + filename) - - # Unconditionally enable 32-bit paths on Windows. - if g_isWindows: - fullpath = '\\\\?\\' + fullpath.replace("/", "\\") + printable_fullpath = (fullpath[4:] if g_isWindows else fullpath) # Get parent directory path. dirpath = os.path.dirname(fullpath) @@ -800,11 +810,11 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None: # Make sure the output filepath doesn't point to an existing directory. if os.path.exists(fullpath) and (not os.path.isfile(fullpath)): utilsResetNspInfo() - g_logger.error(f'Output filepath points to an existing directory! ("{fullpath}").\n') + g_logger.error(f'Output filepath points to an existing directory! ("{printable_fullpath}").\n') return USB_STATUS_HOST_IO_ERROR # Make sure we have enough free space. - (total_space, used_space, free_space) = shutil.disk_usage(dirpath) + (_, _, free_space) = shutil.disk_usage(dirpath) if free_space <= file_size: utilsResetNspInfo() g_logger.error('Not enough free space available in output volume!\n') @@ -826,12 +836,8 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None: # Retrieve what we need using global variables. file = g_nspFile fullpath = g_nspFilePath - - # Unconditionally enable 32-bit paths on Windows. - if g_isWindows: - fullpath = '\\\\?\\' + fullpath.replace("/", "\\") - dirpath = os.path.dirname(fullpath) + printable_fullpath = (fullpath[4:] if g_isWindows else fullpath) # Check if we're dealing with an empty file or with the first SendFileProperties command from a NSP. if (not file_size) or (g_nspTransferMode and file_size == g_nspSize): @@ -846,7 +852,7 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None: usbSendStatus(USB_STATUS_SUCCESS) # Start data transfer stage. - g_logger.debug(f'Data transfer started. Saving {file_type_str} to: "{fullpath}".') + g_logger.debug(f'Data transfer started. Saving {file_type_str} to: "{printable_fullpath}".') offset = 0 blksize = USB_TRANSFER_BLOCK_SIZE @@ -858,10 +864,7 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None: # We're not using dynamic tqdm prefixes under CLI mode. prefix = '' else: - idx = filename.rfind(os.path.sep) - prefix_filename = (filename[idx+1:] if (idx >= 0) else filename) - - prefix = f'Current {file_type_str}: "{prefix_filename}".\n' + prefix = f'Current {file_type_str}: "{os.path.basename(filename)}".\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): @@ -885,8 +888,13 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None: def cancelTransfer(): # Cancel file transfer. - utilsResetNspInfo(True) - if use_pbar: + if g_nspTransferMode: + utilsResetNspInfo(True) + else: + file.close() + os.remove(fullpath) + + if use_pbar and (g_progressBarWindow is not None): g_progressBarWindow.end() # Start transfer process. @@ -917,7 +925,7 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None: # Check if we're dealing with a CancelFileTransfer command. if chunk_size == USB_CMD_HEADER_SIZE: - (magic, cmd_id, cmd_block_size) = struct.unpack_from('<4sII', chunk, 0) + (magic, cmd_id, _) = struct.unpack_from('<4sII', chunk, 0) if (magic == USB_MAGIC_WORD) and (cmd_id == USB_CMD_CANCEL_FILE_TRANSFER): # Cancel file transfer. cancelTransfer() @@ -957,7 +965,7 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None: return USB_STATUS_SUCCESS 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.') @@ -972,8 +980,8 @@ def usbHandleCancelFileTransfer(cmd_block: bytes) -> int: def usbHandleSendNspHeader(cmd_block: bytes) -> int: global g_nspTransferMode, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath - #assert g_logger is not None - #assert g_nspFile is not None + assert g_logger is not None + assert g_nspFile is not None nsp_header_size = len(cmd_block) @@ -1004,12 +1012,12 @@ def usbHandleSendNspHeader(cmd_block: bytes) -> int: return USB_STATUS_SUCCESS def usbHandleEndSession(cmd_block: bytes) -> int: - #assert g_logger is not None + assert g_logger is not None g_logger.debug(f'Received EndSession ({USB_CMD_END_SESSION:02X}) command.') return USB_STATUS_SUCCESS def usbHandleStartExtractedFsDump(cmd_block: bytes) -> int: - #assert g_logger is not None + assert g_logger is not None g_logger.debug(f'Received StartExtractedFsDump ({USB_CMD_START_EXTRACTED_FS_DUMP:02X}) command.') @@ -1027,13 +1035,13 @@ def usbHandleStartExtractedFsDump(cmd_block: bytes) -> int: return USB_STATUS_SUCCESS def usbHandleEndExtractedFsDump(cmd_block: bytes) -> int: - #assert g_logger is not None + assert g_logger is not None g_logger.debug(f'Received EndExtractedFsDump ({USB_CMD_END_EXTRACTED_FS_DUMP:02X}) command.') g_logger.info(f'Finished extracted FS dump.') return USB_STATUS_SUCCESS def usbCommandHandler() -> None: - #assert g_logger is not None + assert g_logger is not None cmd_dict = { USB_CMD_START_SESSION: usbHandleStartSession, @@ -1054,8 +1062,8 @@ def usbCommandHandler() -> None: if not g_cliMode: # Update UI. - #assert g_tkCanvas is not None - #assert g_tkServerButton is not None + assert g_tkCanvas is not None + assert g_tkServerButton is not None g_tkCanvas.itemconfigure(g_tkTipMessage, state='normal', text=SERVER_STOP_MSG) g_tkServerButton.configure(state='disabled') @@ -1123,13 +1131,13 @@ def usbCommandHandler() -> None: def uiStopServer() -> None: # Signal the shared stop event. - #assert g_stopEvent is not None + assert g_stopEvent is not None g_stopEvent.set() def uiStartServer() -> None: global g_outputDir - #assert g_tkDirText is not None + assert g_tkDirText is not None g_outputDir = g_tkDirText.get('1.0', tk.END).strip() if not g_outputDir: @@ -1137,6 +1145,10 @@ def uiStartServer() -> None: messagebox.showerror('Error', 'You must provide an output directory!', parent=g_tkRoot) return + # Unconditionally enable 32-bit paths on Windows. + if g_isWindows: + g_outputDir = '\\\\?\\' + g_outputDir + # Make sure the full directory tree exists. try: os.makedirs(g_outputDir, exist_ok=True) @@ -1153,11 +1165,11 @@ def uiStartServer() -> None: server_thread.start() def uiToggleElements(flag: bool) -> None: - #assert g_tkRoot is not None - #assert g_tkChooseDirButton is not None - #assert g_tkServerButton is not None - #assert g_tkCanvas is not None - #assert g_tkVerboseCheckbox is not None + assert g_tkRoot is not None + assert g_tkChooseDirButton is not None + assert g_tkServerButton is not None + assert g_tkCanvas is not None + assert g_tkVerboseCheckbox is not None if flag: g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocol) @@ -1168,7 +1180,7 @@ def uiToggleElements(flag: bool) -> None: g_tkVerboseCheckbox.configure(state='normal') else: - #assert g_tkScrolledTextLog is not None + assert g_tkScrolledTextLog is not None g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocolStub) @@ -1188,14 +1200,14 @@ def uiChooseDirectory() -> None: uiUpdateDirectoryField(os.path.abspath(dir)) def uiUpdateDirectoryField(path: str) -> None: - #assert g_tkDirText is not None + assert g_tkDirText is not None g_tkDirText.configure(state='normal') g_tkDirText.delete('1.0', tk.END) g_tkDirText.insert('1.0', path) g_tkDirText.configure(state='disabled') def uiHandleExitProtocol() -> None: - #assert g_tkRoot is not None + assert g_tkRoot is not None g_tkRoot.destroy() def uiHandleExitProtocolStub() -> None: @@ -1205,8 +1217,8 @@ def uiScaleMeasure(measure: int) -> int: return round(float(measure) * SCALE) def uiHandleVerboseCheckbox() -> None: - #assert g_logger is not None - #assert g_logLevelIntVar is not None + assert g_logger is not None + assert g_logLevelIntVar is not None g_logger.setLevel(g_logLevelIntVar.get()) def uiInitialize() -> None: @@ -1340,9 +1352,9 @@ def uiInitialize() -> None: g_tkRoot.mainloop() def cliInitialize() -> None: - global g_progressBarWindow + global g_progressBarWindow, g_outputDir - #assert g_logger is not None + assert g_logger is not None # Initialize console logger. console = LogConsole() @@ -1352,8 +1364,12 @@ def cliInitialize() -> None: g_progressBarWindow = ProgressBarWindow(bar_format) # Print info. - g_logger.info('\n' + SCRIPT_TITLE + '. ' + COPYRIGHT_TEXT + '.') - g_logger.info('Output directory: "' + g_outputDir + '".\n') + g_logger.info(f'\n{SCRIPT_TITLE}. {COPYRIGHT_TEXT}.') + g_logger.info(f'Output directory: "{g_outputDir}".\n') + + # Unconditionally enable 32-bit paths on Windows. + if g_isWindows: + g_outputDir = '\\\\?\\' + g_outputDir # Start USB command handler directly. usbCommandHandler() @@ -1365,9 +1381,9 @@ def main() -> int: warnings.filterwarnings("ignore") # Parse command line arguments. - parser = ArgumentParser(description=SCRIPT_TITLE + '. ' + COPYRIGHT_TEXT + '.') + parser = ArgumentParser(description=f'{SCRIPT_TITLE}. {COPYRIGHT_TEXT}.') parser.add_argument('-c', '--cli', required=False, action='store_true', default=False, help='Start the script in CLI mode.') - parser.add_argument('-o', '--outdir', required=False, type=str, metavar='DIR', help='Path to output directory. Defaults to "' + DEFAULT_DIR + '".') + parser.add_argument('-o', '--outdir', required=False, type=str, metavar='DIR', help=f'Path to output directory. Defaults to "{DEFAULT_DIR}".') parser.add_argument('-v', '--verbose', required=False, action='store_true', default=False, help='Enable verbose output.') args = parser.parse_args() @@ -1415,7 +1431,7 @@ if __name__ == "__main__": except KeyboardInterrupt: time.sleep(0.2) print('\nScript interrupted.') - except Exception as e: + except: utilsLogException(traceback.format_exc()) try: diff --git a/host/windows_make_standalone.bat b/host/windows_make_standalone.bat index 02d44f9..3651777 100644 --- a/host/windows_make_standalone.bat +++ b/host/windows_make_standalone.bat @@ -3,26 +3,22 @@ set scriptdir=%~dp0 set scriptdir=%scriptdir:~0,-1% -set venvname=installer +set venvname=standalone set venvscripts=%scriptdir%\%venvname%\Scripts set venvpython=%venvscripts%\python.exe -set venvpyinstaller=%venvscripts%\pyinstaller.exe cd /D "%scriptdir%" python -m venv "%venvname%" -"%venvpython%" -m pip install --upgrade pyinstaller -r requirements-win32.txt +"%venvpython%" -m pip install --upgrade nuitka -r requirements-win32.txt -"%venvpyinstaller%" -y --clean --log-level WARN -F --add-binary "C:\Windows\System32\libusb0.dll;." -w -i nxdt.ico nxdt_host.py +"%venvpython%" -m nuitka --standalone --onefile --deployment --disable-console --windows-icon-from-ico=nxdt.ico --enable-plugin=tk-inter nxdt_host.py -move dist\nxdt_host.exe nxdt_host.exe - -rmdir /s /q __pycache__ -rmdir /s /q build -rmdir /s /q dist -rmdir /s /q installer -del nxdt_host.spec +rmdir /s /q nxdt_host.build +rmdir /s /q nxdt_host.dist +rmdir /s /q nxdt_host.onefile-build +rmdir /s /q standalone pause