mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-23 02:36:41 +00:00
merge conflicts, missed cases, printing improvements
This commit is contained in:
parent
519831ec8f
commit
955821c45b
1 changed files with 200 additions and 122 deletions
|
@ -367,11 +367,13 @@ g_nspFile: Optional[BufferedWriter] = None
|
|||
g_nspFilePath: str = ''
|
||||
|
||||
g_extractedFsDumpMode: bool = False
|
||||
g_layeredFsDumpMode: bool = False
|
||||
|
||||
g_formattedFileSize: float = 0
|
||||
g_fileSizeMiB: float = 0
|
||||
g_formattedFileUnit: str = 'B'
|
||||
g_startTime: float = 0
|
||||
g_extractedFsAbsRoot: str = ""
|
||||
|
||||
|
||||
|
||||
|
@ -434,7 +436,7 @@ 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
|
||||
assert g_logger is not None
|
||||
|
||||
self.scrolled_text = scrolled_text
|
||||
self.frame = (self.scrolled_text.winfo_toplevel() if self.scrolled_text else None)
|
||||
|
@ -555,8 +557,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)
|
||||
|
@ -581,7 +583,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)
|
||||
|
||||
|
@ -598,7 +600,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)
|
||||
|
@ -611,10 +613,9 @@ 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()
|
||||
|
||||
def set_prefix(self, prefix) -> None:
|
||||
self.prefix = prefix
|
||||
|
@ -647,19 +648,23 @@ def utilsGetPath(path_arg: str, fallback_path: str, is_file: bool, create: bool
|
|||
# ref0: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#win32-file-namespaces
|
||||
# ref1: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
|
||||
# ref2: https://stackoverflow.com/a/15373771
|
||||
# Replaces '/' separator with proper '\' in case running under MSYS2 env.
|
||||
# Also replaces '/' separator with proper '\' in case running under MSYS2 env.
|
||||
|
||||
def utilsGetWinFullPath(path_arg: str) -> str:
|
||||
return '\\\\?\\' + path_arg.replace("/", "\\")
|
||||
global g_isWindows
|
||||
return ('\\\\?\\' + path_arg.replace("/", "\\")) if g_isWindows else path_arg
|
||||
|
||||
# Strips the preceding prefix when we want to print
|
||||
|
||||
def utilsStripWinPrefix(path_arg: str) -> str:
|
||||
global g_isWindows
|
||||
return path_arg[4:] if g_isWindows else path_arg
|
||||
|
||||
# Updates the path of the log
|
||||
def utilsUpdateLogPath() -> None:
|
||||
global g_logPath
|
||||
|
||||
g_logPath = os.path.abspath(g_outputDir + os.path.sep + \
|
||||
"nxdt_host_" + datetime.now().strftime('%Y-%m-%d_%H%M%S') + '.log')
|
||||
if g_isWindows:
|
||||
g_logPath = utilsGetWinFullPath(g_logPath)
|
||||
|
||||
"nxdt_host_" + datetime.now().strftime('%Y-%m-%d_%H%M%S') + '.log')
|
||||
return
|
||||
|
||||
# Enable terminal colors on *nix and supported Windows (10.0.10586+)
|
||||
|
@ -689,12 +694,17 @@ def utilsSetupTerminal() -> None:
|
|||
print(COLOR_BACKGROUND)
|
||||
|
||||
# Log basic info about the script and settings.
|
||||
def utilsLogBasicInfo() -> None:
|
||||
global g_logToFile, g_logVerbose, g_pathSep
|
||||
def utilsLogBasicScriptInfo() -> None:
|
||||
global g_osType, g_osVersion, g_logToFile, g_logVerbose, g_pathSep, g_isWindows
|
||||
g_logger.info('\n' + SCRIPT_TITLE + '. ' + COPYRIGHT_TEXT + '.')
|
||||
g_logger.info('\nServer started...\n')
|
||||
g_logger.info('Sys:\tPython ' + platform.python_version() + " on "+ g_osType+" "+g_osVersion)
|
||||
g_logger.info('Dst:\t' + g_outputDir)
|
||||
|
||||
# if g_isWindows:
|
||||
# g_logger.info('Dst:\t' + utilsStripWinPrefix(g_outputDir))
|
||||
# else:
|
||||
# g_logger.info('Dst:\t' + g_outputDir)
|
||||
g_logger.info('Dst:\t' + utilsStripWinPrefix(g_outputDir))
|
||||
|
||||
if g_logToFile:
|
||||
g_logger.info('Log:\t' + g_logPath.rsplit(g_pathSep, 1)[-1])
|
||||
|
@ -710,11 +720,12 @@ def utilsLogBasicInfo() -> None:
|
|||
|
||||
# On successful transfer, log elapsed time and (within reason) average transfer speed
|
||||
def utilsLogTransferStats(elapsed_time: float) -> None:
|
||||
if g_formattedFileUnit == "GiB" or g_formattedFileUnit == "MiB":
|
||||
global g_formattedFileSize, g_formattedFileUnit, g_fileSizeMiB
|
||||
if g_formattedFileUnit != "B":
|
||||
formatted_time = f'{elapsed_time:.2f}s' if round(elapsed_time < 60) else tqdm.format_interval(elapsed_time)
|
||||
g_logger.info(f'{g_formattedFileSize:.2f}{g_formattedFileUnit} transferred in {formatted_time}.')
|
||||
g_logger.info(f'{g_formattedFileSize:.2f} {g_formattedFileUnit} transferred in {formatted_time}.')
|
||||
if elapsed_time > float(1):
|
||||
g_logger.info(f'Avg speed: {g_fileSizeMiB/elapsed_time:.2f}MiB/s\n')
|
||||
g_logger.info(f'Avg speed: {g_fileSizeMiB/elapsed_time:.2f} MiB/s\n')
|
||||
else:
|
||||
g_logger.info(" ")
|
||||
|
||||
|
@ -762,8 +773,8 @@ def utilsInitTransferVars(file_size: int) -> None:
|
|||
def usbGetDeviceEndpoints() -> bool:
|
||||
global g_usbEpIn, g_usbEpOut, g_usbEpMaxPacketSize, g_usbVer
|
||||
|
||||
#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
|
||||
usb_ep_in_lambda = lambda ep: usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN
|
||||
|
@ -828,7 +839,6 @@ def usbGetDeviceEndpoints() -> bool:
|
|||
return True
|
||||
|
||||
def usbRead(size: int, timeout: int = -1) -> bytes:
|
||||
#assert g_logger is not None
|
||||
|
||||
rd = b''
|
||||
|
||||
|
@ -838,12 +848,12 @@ 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.\n')
|
||||
if g_logger is not None:
|
||||
g_logger.error('\nUSB timeout triggered or console disconnected.\n')
|
||||
|
||||
return rd
|
||||
|
||||
def usbWrite(data: bytes, timeout: int = -1) -> int:
|
||||
#assert g_logger is not None
|
||||
|
||||
wr = 0
|
||||
|
||||
|
@ -852,7 +862,8 @@ 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.\n')
|
||||
if g_logger is not None:
|
||||
g_logger.error('\nUSB timeout triggered or console disconnected.\n')
|
||||
|
||||
return wr
|
||||
|
||||
|
@ -863,7 +874,7 @@ def usbSendStatus(code: int) -> bool:
|
|||
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
|
||||
|
||||
g_logger.debug(f'\nReceived StartSession ({USB_CMD_START_SESSION:02X}) command.')
|
||||
|
||||
|
@ -896,8 +907,8 @@ 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
|
||||
global g_formattedFileSize, g_formattedFileUnit, g_fileSizeMiB, g_startTime
|
||||
|
||||
#assert g_logger is not None
|
||||
#assert g_progressBarWindow is not None
|
||||
assert g_logger is not None
|
||||
assert g_progressBarWindow is not None
|
||||
|
||||
g_logger.debug(f'\nReceived SendFileProperties ({USB_CMD_SEND_FILE_PROPERTIES:02X}) command.')
|
||||
|
||||
|
@ -907,40 +918,80 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
|
|||
file_type_str = ('file' if (not g_nspTransferMode) else 'NSP file entry')
|
||||
|
||||
|
||||
# Print info (debug / verbose).
|
||||
# Log basic file info (debug / verbose).
|
||||
dbg_str = f'File size: 0x{file_size:X} | Filename length: 0x{filename_length:X}'
|
||||
if nsp_header_size > 0:
|
||||
dbg_str += f' | NSP header size: 0x{nsp_header_size:X}'
|
||||
g_logger.debug(dbg_str + '.')
|
||||
|
||||
# Log basic file info
|
||||
if not g_nspTransferMode and not g_extractedFsDumpMode:
|
||||
ext = filename[-3:]
|
||||
match ext:
|
||||
case "xci": g_logger.info("\tXCI transfer started!")
|
||||
case "bin": g_logger.info("\tGamecard extra data transfer started!")
|
||||
case "fs0": g_logger.info("\tHFS0 raw partition transfer started!")
|
||||
case "nsp": g_logger.info("\tNSP transfer started!")
|
||||
case "nca": g_logger.info("\tRaw NCA transfer started!")
|
||||
case "tik": g_logger.info("\tTicket transfer started!")
|
||||
case _: g_logger.info("\tTransfer of unknown data type started!") # uh-oh?
|
||||
utilsInitTransferVars(file_size)
|
||||
g_logger.info(f'\nFile:\t{filename}')
|
||||
g_logger.info(f'Size:\t{g_formattedFileSize:.2f} {g_formattedFileUnit}')
|
||||
if(ext == "nsp"):
|
||||
g_logger.info(f'Contents:')
|
||||
else:
|
||||
(unit,div) = utilsGetSizeUnitAndDivisor(file_size)
|
||||
fs = file_size / div
|
||||
if g_extractedFsDumpMode:
|
||||
path_array = filename.split("/")
|
||||
match path_array[1]:
|
||||
case 'NCA FS': fn = '/'.join(path_array[7:])
|
||||
case 'HFS': fn = '/'.join(path_array[4:])
|
||||
case _: fn = '/'.join(path_array[1:])
|
||||
elif g_nspTransferMode:
|
||||
fn = filename
|
||||
g_logger.info(f'\t{fn} ({fs:.2f} {unit})')
|
||||
# Log basic file info (verbose off):
|
||||
if not g_logVerbose:
|
||||
if not g_nspTransferMode and not g_extractedFsDumpMode:
|
||||
fp = filename.split('/') # fp[0] is '/' character
|
||||
fp_len = len(fp)
|
||||
ext = filename[-3:]
|
||||
utilsInitTransferVars(file_size)
|
||||
g_logger.info("\nTransfer started!")
|
||||
if (fp_len >= 2): # if file has parent directories
|
||||
match fp[1]: # check parent dir and ext to find type
|
||||
case 'NSP' | 'Ticket':
|
||||
g_logger.info(f'\nType:\t{fp[1]}')
|
||||
g_logger.info(f'Src:\t{fp[2][:fp[2].index("[")]}')
|
||||
g_logger.info(f'\t{fp[2][fp[2].find("["):fp[2].rfind(".")]}')
|
||||
g_logger.info(f'Size:\t{g_formattedFileSize:.2f} {g_formattedFileUnit}')
|
||||
if(ext == 'nsp'): g_logger.info(f'Contents:')
|
||||
case 'Gamecard':
|
||||
g_logger.info(f'\nType:\t{fp[1]} [{ext.upper()}]')
|
||||
g_logger.info(f'Src:\t{fp[2][:fp[2].index("[")]}')
|
||||
g_logger.info(f'\t{fp[2][fp[2].find("["):fp[2].rfind(".")]}')
|
||||
g_logger.info(f'Size:\t{g_formattedFileSize:.2f} {g_formattedFileUnit}')
|
||||
if(ext == 'xci'): g_logger.info(" ")
|
||||
case 'NCA FS':
|
||||
g_logger.info(f'\nType:\t{fp[1]} ({fp[2]} {fp[3]}) [{ext.upper()}]')
|
||||
g_logger.info(f'Src:\t{fp[4][:fp[4].index("[")]}')
|
||||
g_logger.info(f'\t{fp[4][fp[4].index("["):]}')
|
||||
g_logger.info(f'\t{fp[5]}, FS section #{fp[6][:fp[6].index(".")]}')
|
||||
g_logger.info(f'Size:\t{g_formattedFileSize:.2f} {g_formattedFileUnit}\n')
|
||||
case 'atmosphere': # System/NCA Dump -> Section Mode && Write Raw Section && Use LayeredFS Dir
|
||||
g_logger.info(f'\nType:\tNCA FS (atmosphere/contents) [{ext.upper()}]')
|
||||
g_logger.info(f'Src:\t{fp[3]}')
|
||||
g_logger.info(f'\t{fp[4][:fp[4].index(".")]}')
|
||||
g_logger.info(f'Size:\t{g_formattedFileSize:.2f} {g_formattedFileUnit}\n')
|
||||
case 'NCA' | 'HFS':
|
||||
printable_ext = fp[fp_len-1][fp[fp_len-1].index(".")+1:] if fp[1] == 'HFS' else ext
|
||||
g_logger.info(f'\nType:\t{fp[1]} ({fp[2]}) [{printable_ext.upper()}]')
|
||||
g_logger.info(f'Src:\t{fp[3][:fp[3].index("[")]}')
|
||||
g_logger.info(f'\t{fp[3][fp[3].index("["):]}')
|
||||
if fp[1] == 'NCA':
|
||||
g_logger.info(f'File:\t{fp[fp_len-1]}')
|
||||
else:
|
||||
g_logger.info(f'\t{fp[fp_len-1][:fp[fp_len-1].index(".")]}')
|
||||
g_logger.info(f'Size:\t{g_formattedFileSize:.2f} {g_formattedFileUnit}\n')
|
||||
case _: # If we ever get here it is time for more work
|
||||
g_logger.warning(f'\n\tNovel source!!! {fp[1]}???')
|
||||
g_logger.info:(f'{filename}')
|
||||
g_logger.info(f'Size:\t{g_formattedFileSize:.2f} {g_formattedFileUnit}\n')
|
||||
else: # if file is just a file with no parent dirs
|
||||
if (ext == 'bin'): # Thusfar the only such case
|
||||
g_logger.info(f'\nType:\tConsole LAFW BLOB or similar (BIN)')
|
||||
g_logger.info(f'File:\t{filename.rsplit('/', 1)[-1]}')
|
||||
g_logger.info(f'Size:\t{g_formattedFileSize:.2f} {g_formattedFileUnit}')
|
||||
else: # If we ever get here it is time for more work
|
||||
g_logger.warning(f'\n\tNovel source!!!')
|
||||
g_logger.info:(f'"{filename}"')
|
||||
else:
|
||||
(unit,div) = utilsGetSizeUnitAndDivisor(file_size)
|
||||
fs = file_size / div
|
||||
if g_extractedFsDumpMode:
|
||||
fp = filename.split("/")
|
||||
match fp[1]:
|
||||
case 'HFS': fn = '/'.join(fp[5:])
|
||||
case 'NCA FS': fn = '/'.join(fp[7:])
|
||||
case 'atmosphere': fn = '/'.join(fp[5:])
|
||||
case _: fn = '/'.join(fp[1:])
|
||||
elif g_nspTransferMode:
|
||||
fn = filename
|
||||
g_logger.info(f'\t{fn} ({fs:.2f} {unit})')
|
||||
|
||||
# Perform validity checks.
|
||||
if (not g_nspTransferMode) and file_size and (nsp_header_size >= file_size):
|
||||
|
@ -970,10 +1021,12 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
|
|||
# Generate full, absolute path to the destination file.
|
||||
fullpath = os.path.abspath(g_outputDir + os.path.sep + filename)
|
||||
|
||||
# Unconditionally enable long paths in Windows.
|
||||
if g_isWindows:
|
||||
fullpath = utilsGetWinFullPath(fullpath);
|
||||
printable_fullpath = (fullpath[4:] if g_isWindows else fullpath)
|
||||
|
||||
# Unconditionally enable long paths in Windows.
|
||||
# if g_isWindows:
|
||||
# fullpath = utilsGetWinFullPath(fullpath);
|
||||
|
||||
# Get parent directory path.
|
||||
dirpath = os.path.dirname(fullpath)
|
||||
|
||||
|
@ -983,7 +1036,7 @@ 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'\nOutput filepath points to an existing directory! ("{fullpath}").\n')
|
||||
g_logger.error(f'\nOutput filepath points to an existing directory! ("{printable_fullpath}").\n')
|
||||
return USB_STATUS_HOST_IO_ERROR
|
||||
|
||||
# Make sure we have enough free space.
|
||||
|
@ -1011,7 +1064,8 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
|
|||
fullpath = g_nspFilePath
|
||||
|
||||
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):
|
||||
# Close file (if needed).
|
||||
|
@ -1024,7 +1078,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
|
||||
|
@ -1134,15 +1188,16 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
|
|||
# Close file handle (if needed); log successful non-constitutent transfer.
|
||||
if not g_nspTransferMode:
|
||||
file.close()
|
||||
if not g_extractedFsDumpMode:
|
||||
if not g_logVerbose:
|
||||
g_logger.info('\n\tTransfer complete!\n')
|
||||
utilsLogTransferStats(elapsed_time);
|
||||
if not g_extractedFsDumpMode and not g_logVerbose:
|
||||
g_logger.info('\nTransfer complete!\n')
|
||||
utilsLogTransferStats(elapsed_time)
|
||||
g_logger.info('Your file may be found here:')
|
||||
g_logger.info(f'{printable_fullpath}\n')
|
||||
|
||||
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'\nReceived CancelFileTransfer ({USB_CMD_START_SESSION:02X}) command.')
|
||||
|
||||
|
@ -1155,10 +1210,10 @@ def usbHandleCancelFileTransfer(cmd_block: bytes) -> int:
|
|||
return USB_STATUS_MALFORMED_CMD
|
||||
|
||||
def usbHandleSendNspHeader(cmd_block: bytes) -> int:
|
||||
global g_nspTransferMode, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath
|
||||
global g_nspTransferMode, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath, g_isWindows
|
||||
|
||||
#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)
|
||||
|
||||
|
@ -1186,8 +1241,11 @@ def usbHandleSendNspHeader(cmd_block: bytes) -> int:
|
|||
g_logger.debug(f'Successfully wrote 0x{nsp_header_size:X}-byte long NSP header to "{g_nspFilePath}".')
|
||||
|
||||
if not g_logVerbose:
|
||||
g_logger.info('\n\tTransfer complete!\n')
|
||||
g_logger.info('\nTransfer complete!\n')
|
||||
utilsLogTransferStats(time.time() - g_startTime)
|
||||
g_logger.info('Your file may be found here:')
|
||||
g_nspFilePath = utilsStripWinPrefix(g_nspFilePath) #if g_isWindows else g_nspFilePath
|
||||
g_logger.info(f'{g_nspFilePath}\n')
|
||||
|
||||
# Disable NSP transfer mode.
|
||||
utilsResetNspInfo()
|
||||
|
@ -1195,13 +1253,13 @@ 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'\nReceived EndSession ({USB_CMD_END_SESSION:02X}) command.')
|
||||
return USB_STATUS_SUCCESS
|
||||
|
||||
def usbHandleStartExtractedFsDump(cmd_block: bytes) -> int:
|
||||
#assert g_logger is not None
|
||||
global g_extractedFsDumpMode, g_formattedFileSize, g_formattedFileUnit, g_fileSizeMiB, g_startTime
|
||||
assert g_logger is not None
|
||||
global g_isWindows, g_outputDir, g_extractedFsDumpMode, g_extractedFsAbsRoot, g_formattedFileSize, g_formattedFileUnit, g_fileSizeMiB, g_startTime
|
||||
|
||||
g_logger.debug(f'\nReceived StartExtractedFsDump ({USB_CMD_START_EXTRACTED_FS_DUMP:02X}) command.')
|
||||
|
||||
|
@ -1214,25 +1272,38 @@ def usbHandleStartExtractedFsDump(cmd_block: bytes) -> int:
|
|||
extracted_fs_root_path = extracted_fs_root_path.decode('utf-8').strip('\x00')
|
||||
|
||||
utilsInitTransferVars(extracted_fs_size)
|
||||
path_array = extracted_fs_root_path.split('/')
|
||||
fp = extracted_fs_root_path.split('/')
|
||||
|
||||
# Non-verbose/debug logging:
|
||||
if not g_logVerbose:
|
||||
match path_array[1]:
|
||||
g_logger.info(f'Extracted FS dump started!')
|
||||
match fp[1]:
|
||||
case 'HFS':
|
||||
g_logger.info(f'\tExtracted FS dump from HFS (Gamecard) started!')
|
||||
g_logger.info(f'\nSrc:\t'+'/'.join(path_array[3:]))
|
||||
g_logger.info(f'\nType:\t{fp[1]} ({fp[2]})')
|
||||
g_logger.info(f'Src:\t{fp[3][:fp[3].index("[")]}')
|
||||
g_logger.info(f'\t{fp[3][fp[3].index("["):]}')
|
||||
g_logger.info(f'\t{fp[4]}')
|
||||
case 'NCA FS':
|
||||
g_logger.info(f'\tExtracted FS dump from NCA FS (NSP) started!')
|
||||
g_logger.info(f'\nSrc:\t{path_array[4]}')
|
||||
g_logger.info(f'\t{path_array[5]}, FS section #{path_array[6]}')
|
||||
g_logger.info(f'\nType:\t{fp[1]} ({fp[2]} {fp[3]})')
|
||||
g_logger.info(f'Src:\t{fp[4][:fp[4].index("[")]}')
|
||||
g_logger.info(f'\t{fp[4][fp[4].index("["):]}')
|
||||
g_logger.info(f'\t{fp[5]}, FS section #{fp[6]}')
|
||||
case 'atmosphere':
|
||||
g_logger.info(f'\nType:\tLayeredFS ('+('/'.join(fp[1:3]))+')')
|
||||
g_logger.info(f'Src:\t{fp[3]}')
|
||||
g_logger.info(f'\t{fp[4]}')
|
||||
case _:
|
||||
g_logger.info(f'\tExtracted FS dump from novel source (???) started!')
|
||||
g_logger.info:(f'\nRoot:\t{extracted_fs_root_path}')
|
||||
g_logger.warning(f'\n\tNovel source!!! {fp[1]}???\n')
|
||||
g_logger.info:(f'\nRoot:\t"{extracted_fs_root_path}"')
|
||||
|
||||
g_logger.info(f'Size:\t{g_formattedFileSize:.2f} {g_formattedFileUnit}')
|
||||
g_logger.info(f'Files:')
|
||||
g_logger.info(f'Contents:')
|
||||
else:
|
||||
g_logger.debug(f'Starting extracted FS dump (size 0x{extracted_fs_size:X}, output relative path "{extracted_fs_root_path}").')
|
||||
|
||||
|
||||
#g_extractedFsAbsRoot = extracted_fs_root_path
|
||||
g_extractedFsAbsRoot = os.path.abspath(g_outputDir + os.path.sep + extracted_fs_root_path)
|
||||
g_extractedFsAbsRoot = utilsStripWinPrefix(g_extractedFsAbsRoot) #if g_isWindows else g_extractedFsAbsRoot
|
||||
g_extractedFsDumpMode = True
|
||||
g_startTime = time.time()
|
||||
|
||||
|
@ -1240,17 +1311,20 @@ def usbHandleStartExtractedFsDump(cmd_block: bytes) -> int:
|
|||
return USB_STATUS_SUCCESS
|
||||
|
||||
def usbHandleEndExtractedFsDump(cmd_block: bytes) -> int:
|
||||
global g_extractedFsDumpMode
|
||||
#assert g_logger is not None
|
||||
g_extractedFsDumpMode = False
|
||||
global g_extractedFsDumpMode, g_extractedFsAbsRoot
|
||||
assert g_logger is not None
|
||||
g_logger.debug(f'\nReceived EndExtractedFsDump ({USB_CMD_END_EXTRACTED_FS_DUMP:02X}) command.')
|
||||
if not g_logVerbose:
|
||||
g_logger.info(f'\n\tExtracted FS dump complete!\n')
|
||||
g_logger.info(f'\nExtracted FS dump complete!\n')
|
||||
utilsLogTransferStats(time.time() - g_startTime)
|
||||
g_logger.info(f'Your files may be found here:')
|
||||
g_logger.info(f'{g_extractedFsAbsRoot}{g_pathSep}\n')
|
||||
g_extractedFsDumpMode = False
|
||||
g_extractedFsAbsRoot = ""
|
||||
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,
|
||||
|
@ -1271,8 +1345,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')
|
||||
|
||||
|
@ -1340,7 +1414,7 @@ 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()
|
||||
# Log the end of the session.
|
||||
g_logger.info("Server stopped.\n")
|
||||
|
@ -1348,27 +1422,28 @@ def uiStopServer() -> None:
|
|||
|
||||
def uiStartServer() -> None:
|
||||
|
||||
uiUpdateOutputDir()
|
||||
# Set new log path for this session if logging to file is turned on.
|
||||
if g_logToFile:
|
||||
utilsUpdateLogPath()
|
||||
|
||||
|
||||
# Update UI.
|
||||
uiToggleElements(False)
|
||||
|
||||
# Log basic info about the script and settings.
|
||||
utilsLogBasicInfo()
|
||||
utilsLogBasicScriptInfo()
|
||||
|
||||
# Create background server thread.
|
||||
server_thread = threading.Thread(target=usbCommandHandler, daemon=True)
|
||||
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_tkLogToFileCheckbox 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_tkLogToFileCheckbox is not None
|
||||
assert g_tkVerboseCheckbox is not None
|
||||
|
||||
if flag:
|
||||
g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocol)
|
||||
|
@ -1379,7 +1454,7 @@ def uiToggleElements(flag: bool) -> None:
|
|||
g_tkLogToFileCheckbox.configure(state='normal')
|
||||
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)
|
||||
|
||||
|
@ -1394,19 +1469,19 @@ def uiToggleElements(flag: bool) -> None:
|
|||
g_tkVerboseCheckbox.configure(state='disabled')
|
||||
|
||||
def uiChooseDirectory() -> None:
|
||||
#assert g_tkDirText is not None
|
||||
assert g_tkDirText is not None
|
||||
dirtext = g_tkDirText.get('1.0', tk.END).strip()
|
||||
initdir = dirtext if os.path.exists(dirtext) else INITIAL_DIR
|
||||
|
||||
initdir = dirtext if os.path.exists(dirtext) else INITIAL_DIR
|
||||
# Using \\?\ longfile syntax for Windows will not work for tkinter.filedialog.askdirectory's initialdir parameter.
|
||||
# User must choose a directory considered the 'normal' length on their system if using the UI.
|
||||
dir = filedialog.askdirectory(parent=g_tkRoot, title='Select an output directory', initialdir=initdir, mustexist=True)
|
||||
|
||||
if dir:
|
||||
uiUpdateDirectoryField(os.path.abspath(dir))
|
||||
uiUpdateOutputDir()
|
||||
|
||||
|
||||
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)
|
||||
|
@ -1415,9 +1490,11 @@ def uiUpdateDirectoryField(path: str) -> None:
|
|||
|
||||
def uiUpdateOutputDir() -> 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()
|
||||
g_outputDir = utilsGetWinFullPath(g_tkDirText.get('1.0', tk.END).strip())
|
||||
# g_outputDir = utilsGetWinFullPath(g_outputDir) if g_isWindows else g_outputDir
|
||||
|
||||
if not g_outputDir:
|
||||
# We should never reach this, honestly.
|
||||
messagebox.showerror('Error', 'You must provide an output directory!', parent=g_tkRoot)
|
||||
|
@ -1434,7 +1511,7 @@ def uiUpdateOutputDir() -> None:
|
|||
return
|
||||
|
||||
def uiHandleExitProtocol() -> None:
|
||||
#assert g_tkRoot is not None
|
||||
assert g_tkRoot is not None
|
||||
g_tkRoot.destroy()
|
||||
|
||||
def uiHandleExitProtocolStub() -> None:
|
||||
|
@ -1444,15 +1521,14 @@ def uiScaleMeasure(measure: int) -> int:
|
|||
return round(float(measure) * SCALE)
|
||||
|
||||
def uiHandleLogToFileCheckbox() -> None:
|
||||
#assert g_logToFile is not None
|
||||
#assert g_logToFileBoolVar is not None
|
||||
assert g_logToFileBoolVar is not None
|
||||
global g_logToFile
|
||||
g_logToFile = g_logToFileBoolVar.get()
|
||||
return
|
||||
|
||||
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
|
||||
global g_logVerbose
|
||||
logLevel=g_logLevelIntVar.get()
|
||||
g_logger.setLevel(logLevel)
|
||||
|
@ -1600,14 +1676,16 @@ def uiInitialize() -> None:
|
|||
g_tkRoot.mainloop()
|
||||
|
||||
def cliInitialize() -> None:
|
||||
global g_progressBarWindow
|
||||
global g_progressBarWindow, g_outputDir, g_logToFile
|
||||
|
||||
#assert g_logger is not None
|
||||
# Unconditionally enable long paths on Windows.
|
||||
g_outputDir = utilsGetWinFullPath(g_outputDir) #if g_isWindows else g_outputDir
|
||||
|
||||
# determines whether to use colors in terminal and sets up accordingly
|
||||
# Determines whether to use colors in terminal and sets up accordingly.
|
||||
utilsSetupTerminal()
|
||||
|
||||
# Set log path if logging to file specified at cmd line
|
||||
# Set log path if logging to file specified at cmd line.
|
||||
# NB, g_outputDir should be adjusted for Windows prior.
|
||||
if g_logToFile:
|
||||
utilsUpdateLogPath()
|
||||
|
||||
|
@ -1619,7 +1697,7 @@ def cliInitialize() -> None:
|
|||
g_progressBarWindow = ProgressBarWindow(bar_format)
|
||||
|
||||
# Log basic info about the script and settings.
|
||||
utilsLogBasicInfo()
|
||||
utilsLogBasicScriptInfo()
|
||||
|
||||
# Start USB command handler directly.
|
||||
usbCommandHandler()
|
||||
|
@ -1691,7 +1769,7 @@ if __name__ == "__main__":
|
|||
g_logger.info("Host script exited!")
|
||||
if g_isWindows10: print(COLOR_RESET)
|
||||
|
||||
except Exception as e:
|
||||
except:
|
||||
utilsLogException(traceback.format_exc())
|
||||
|
||||
try:
|
||||
|
|
Loading…
Reference in a new issue