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/code_templates/nxdt_rw_poc.c b/code_templates/nxdt_rw_poc.c index d9dd1e8..a0171e0 100644 --- a/code_templates/nxdt_rw_poc.c +++ b/code_templates/nxdt_rw_poc.c @@ -43,6 +43,7 @@ typedef bool (*MenuElementFunction)(void *userdata); typedef struct { u32 selected; ///< Used to keep track of the selected option. + bool retrieved; ///< Used to determine if the value for this option has already been retrieved from configuration. MenuElementOptionGetterFunction getter_func; ///< Pointer to a function to be called the first time an option value is loaded. Should be set to NULL if not used. MenuElementOptionSetterFunction setter_func; ///< Pointer to a function to be called each time a new option value is selected. Should be set to NULL if not used. char **options; ///< Pointer to multiple char pointers with strings representing options. Last element must be set to NULL. @@ -147,6 +148,7 @@ static void consolePrintReversedColors(const char *text, ...); static void consoleRefresh(void); static u32 menuGetElementCount(const Menu *menu); +static void menuResetAttributes(Menu *cur_menu, u32 element_count); void freeStorageList(void); void updateStorageList(void); @@ -177,6 +179,8 @@ static char *generateOutputLayeredFsFileName(u64 title_id, const char *subdir, c static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out); +static bool resetSettings(void *userdata); + static bool saveGameCardImage(void *userdata); static bool saveGameCardHeader(void *userdata); static bool saveGameCardCardInfo(void *userdata); @@ -289,6 +293,7 @@ static char **g_storageOptions = NULL; static MenuElementOption g_storageMenuElementOption = { .selected = 0, + .retrieved = false, .getter_func = &getOutputStorageOption, .setter_func = &setOutputStorageOption, .options = NULL // Dynamically set @@ -316,6 +321,7 @@ static MenuElement *g_xciMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 0, + .retrieved = false, .getter_func = &getGameCardPrependKeyAreaOption, .setter_func = &setGameCardPrependKeyAreaOption, .options = g_noYesStrings @@ -328,6 +334,7 @@ static MenuElement *g_xciMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 0, + .retrieved = false, .getter_func = &getGameCardKeepCertificateOption, .setter_func = &setGameCardKeepCertificateOption, .options = g_noYesStrings @@ -340,6 +347,7 @@ static MenuElement *g_xciMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 0, + .retrieved = false, .getter_func = &getGameCardTrimDumpOption, .setter_func = &setGameCardTrimDumpOption, .options = g_noYesStrings @@ -352,6 +360,7 @@ static MenuElement *g_xciMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 1, + .retrieved = false, .getter_func = &getGameCardCalculateChecksumOption, .setter_func = &setGameCardCalculateChecksumOption, .options = g_noYesStrings @@ -410,6 +419,7 @@ static MenuElement *g_gameCardHfsMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 0, + .retrieved = false, .getter_func = &getGameCardWriteRawHfsPartitionOption, .setter_func = &setGameCardWriteRawHfsPartitionOption, .options = g_noYesStrings @@ -514,6 +524,7 @@ static MenuElement *g_nspMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 0, + .retrieved = false, .getter_func = &getNspSetDownloadDistributionOption, .setter_func = &setNspSetDownloadDistributionOption, .options = g_noYesStrings @@ -526,6 +537,7 @@ static MenuElement *g_nspMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 0, + .retrieved = false, .getter_func = &getNspRemoveConsoleDataOption, .setter_func = &setNspRemoveConsoleDataOption, .options = g_noYesStrings @@ -538,6 +550,7 @@ static MenuElement *g_nspMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 0, + .retrieved = false, .getter_func = &getNspRemoveTitlekeyCryptoOption, .setter_func = &setNspRemoveTitlekeyCryptoOption, .options = g_noYesStrings @@ -550,6 +563,7 @@ static MenuElement *g_nspMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 1, + .retrieved = false, .getter_func = &getNspDisableLinkedAccountRequirementOption, .setter_func = &setNspDisableLinkedAccountRequirementOption, .options = g_noYesStrings @@ -562,6 +576,7 @@ static MenuElement *g_nspMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 1, + .retrieved = false, .getter_func = &getNspEnableScreenshotsOption, .setter_func = &setNspEnableScreenshotsOption, .options = g_noYesStrings @@ -574,6 +589,7 @@ static MenuElement *g_nspMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 1, + .retrieved = false, .getter_func = &getNspEnableVideoCaptureOption, .setter_func = &setNspEnableVideoCaptureOption, .options = g_noYesStrings @@ -586,6 +602,7 @@ static MenuElement *g_nspMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 1, + .retrieved = false, .getter_func = &getNspDisableHdcpOption, .setter_func = &setNspDisableHdcpOption, .options = g_noYesStrings @@ -598,6 +615,7 @@ static MenuElement *g_nspMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 1, + .retrieved = false, .getter_func = &getNspGenerateAuthoringToolDataOption, .setter_func = &setNspGenerateAuthoringToolDataOption, .options = g_noYesStrings @@ -630,6 +648,7 @@ static MenuElement *g_ticketMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 0, + .retrieved = false, .getter_func = &getTicketRemoveConsoleDataOption, .setter_func = &setTicketRemoveConsoleDataOption, .options = g_noYesStrings @@ -653,6 +672,7 @@ static char **g_ncaBasePatchOptions = NULL; static MenuElementOption g_ncaFsSectionsSubMenuBasePatchElementOption = { .selected = 0, + .retrieved = false, .getter_func = NULL, .setter_func = NULL, .options = NULL // Dynamically set @@ -679,6 +699,7 @@ static MenuElement *g_ncaFsSectionsSubMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 0, + .retrieved = false, .getter_func = &getNcaFsWriteRawSectionOption, .setter_func = &setNcaFsWriteRawSectionOption, .options = g_noYesStrings @@ -691,6 +712,7 @@ static MenuElement *g_ncaFsSectionsSubMenuElements[] = { .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 0, + .retrieved = false, .getter_func = &getNcaFsUseLayeredFsDirOption, .setter_func = &setNcaFsUseLayeredFsDirOption, .options = g_noYesStrings @@ -869,6 +891,13 @@ static MenuElement *g_rootMenuElements[] = { .element_options = NULL, .userdata = NULL }, + &(MenuElement){ + .str = "reset settings", + .child_menu = NULL, + .task_func = &resetSettings, + .element_options = NULL, + .userdata = NULL + }, NULL }; @@ -941,6 +970,7 @@ int main(int argc, char *argv[]) consolePrint("______________________________\n\n"); if (cur_menu->parent) consolePrint("press b to go back\n"); if (g_umsDeviceCount) consolePrint("press x to safely remove all ums devices\n"); + consolePrint("use the sticks to scroll faster\n"); consolePrint("press + to exit\n"); consolePrint("______________________________\n\n"); @@ -1032,10 +1062,10 @@ int main(int argc, char *argv[]) if (cur_options) { - if (cur_options->getter_func) + if (cur_options->getter_func && !cur_options->retrieved) { cur_options->selected = cur_options->getter_func(); - cur_options->getter_func = NULL; + cur_options->retrieved = true; } consolePrint(": "); @@ -1179,7 +1209,6 @@ int main(int argc, char *argv[]) if (!error) { child_menu->parent = cur_menu; - child_menu->selected = child_menu->scroll = 0; cur_menu = child_menu; element_count = menuGetElementCount(cur_menu); @@ -1200,23 +1229,29 @@ int main(int argc, char *argv[]) break; } - /* Wait for USB session (if needed). */ - if (useUsbHost() && !waitForUsb()) + if (cur_menu->id > MenuId_Root) { - if (g_appletStatus) continue; - break; + /* Wait for USB session (if needed). */ + if (useUsbHost() && !waitForUsb()) + { + if (g_appletStatus) continue; + break; + } + + /* Run task. */ + utilsSetLongRunningProcessState(true); + + if (selected_element->task_func(selected_element->userdata)) + { + if (!useUsbHost()) updateStorageList(); // update free space + } + + utilsSetLongRunningProcessState(false); + } else { + /* Ignore result. */ + selected_element->task_func(selected_element->userdata); } - /* Run task. */ - utilsSetLongRunningProcessState(true); - - if (selected_element->task_func(selected_element->userdata)) - { - if (!useUsbHost()) updateStorageList(); // update free space - } - - utilsSetLongRunningProcessState(false); - /* Display prompt. */ consolePrint("press any button to continue"); utilsWaitForButtonPress(0); @@ -1302,6 +1337,8 @@ int main(int argc, char *argv[]) } else if ((btn_down & HidNpadButton_B) && cur_menu->parent) { + menuResetAttributes(cur_menu, element_count); + if (cur_menu->id == MenuId_UserTitles || cur_menu->id == MenuId_SystemTitles) { app_metadata = NULL; @@ -1344,9 +1381,6 @@ int main(int argc, char *argv[]) freeNcaBasePatchList(); } - cur_menu->selected = 0; - cur_menu->scroll = 0; - cur_menu = cur_menu->parent; element_count = menuGetElementCount(cur_menu); } else @@ -1483,6 +1517,21 @@ static u32 menuGetElementCount(const Menu *menu) return cnt; } +static void menuResetAttributes(Menu *cur_menu, u32 element_count) +{ + if (!cur_menu) return; + + cur_menu->selected = 0; + cur_menu->scroll = 0; + + for(u32 i = 0; i < element_count; i++) + { + MenuElement *cur_element = cur_menu->elements[i]; + MenuElementOption *cur_options = cur_element->element_options; + if (cur_options && cur_options != &g_storageMenuElementOption) cur_options->retrieved = false; + } +} + void freeStorageList(void) { u32 elem_count = (2 + g_umsDeviceCount); // sd card, usb host, ums devices @@ -1638,7 +1687,12 @@ end: static TitleInfo *getLatestTitleInfo(TitleInfo *title_info, u32 *out_idx, u32 *out_count) { - if (!title_info || !out_idx || !out_count || (title_info->meta_key.type != NcmContentMetaType_Patch && title_info->meta_key.type != NcmContentMetaType_DataPatch)) return title_info; + if (!title_info || !out_idx || !out_count || (title_info->meta_key.type != NcmContentMetaType_Patch && title_info->meta_key.type != NcmContentMetaType_DataPatch)) + { + if (out_idx) *out_idx = 0; + if (out_count) *out_count = titleGetCountFromInfoBlock(title_info); + return title_info; + } u32 idx = 0, count = 1; TitleInfo *cur_info = title_info->previous, *out = title_info; @@ -2241,6 +2295,21 @@ static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out) return true; } +static bool resetSettings(void *userdata) +{ + consolePrint("are you sure you want to reset all settings to their default values?\n"); + consolePrint("press a to proceed, or b to cancel\n\n"); + + u64 btn_down = utilsWaitForButtonPress(HidNpadButton_A | HidNpadButton_B); + if (btn_down & HidNpadButton_A) + { + configResetSettings(); + consolePrint("settings successfully reset\n"); + } + + return false; +} + static bool saveGameCardImage(void *userdata) { (void)userdata; @@ -4964,8 +5033,19 @@ static void nspThreadFunc(void *arg) consolePrintReversedColors("\nif you proceed, nca modifications will be disabled, and content decryption"); consolePrintReversedColors("\nwill not be possible for external tools (e.g. emulators, etc.)\n"); - consolePrintReversedColors("\nif this is a shared game and you wish to fix this, exit the application and"); - consolePrintReversedColors("\ntry running it at least once, then come back and try again\n"); + consolePrintReversedColors("\nthis may occur because of different reasons:\n"); + + consolePrintReversedColors("\n1. you haven't launched this game/dlc at least once since you downloaded it"); + consolePrintReversedColors("\n2. this is a shared game/dlc across different switch consoles using the"); + consolePrintReversedColors("\n same nintendo account and you're using the secondary console"); + consolePrintReversedColors("\n3. you downloaded this game/dlc onto your sd card using your sysmmc, then"); + consolePrintReversedColors("\n copied the 'nintendo' folder data into the 'emummc' folder (or viceversa)\n"); + + consolePrintReversedColors("\ncases 1 and 2 can be fixed by exiting nxdumptool, launching the game"); + consolePrintReversedColors("\nand then running nxdumptool once again\n"); + + consolePrintReversedColors("\ncase 3 can be fixed by running nxdumptool directly under the emmc that was"); + consolePrintReversedColors("\nused to download the game/dlc\n"); consolePrintReversedColors("\npress a to proceed anyway, or b to cancel\n\n"); 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 8738aab..c5b6199 100644 --- a/host/nxdt_host.py +++ b/host/nxdt_host.py @@ -58,7 +58,7 @@ from tqdm import tqdm from argparse import ArgumentParser, RawTextHelpFormatter, ArgumentDefaultsHelpFormatter from io import BufferedWriter -from typing import List, Tuple, Any, Callable, Optional +from typing import Generator, Any, Callable from datetime import datetime @@ -149,8 +149,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. @@ -237,7 +237,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' + \ @@ -318,11 +318,11 @@ g_logToFile: bool = False g_logVerbose: bool = False g_terminalColors: bool = False g_outputDir: str = '' +g_logLevelIntVar: tk.IntVar | None = None +g_logToFileBoolVar: tk.BooleanVar | None = None g_logPath: str = '' g_pathSep: str = '' -g_logLevelIntVar: tk.IntVar | None = None -g_logToFileBoolVar: tk.BooleanVar | None = None g_osType: str = '' g_osVersion: str = '' @@ -331,18 +331,18 @@ g_isWindowsVista: bool = False g_isWindows7: bool = False g_isWindows10: 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 @@ -363,7 +363,7 @@ 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 = '' g_extractedFsDumpMode: bool = False @@ -378,7 +378,7 @@ g_extractedFsAbsRoot: 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 @@ -481,7 +481,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 @@ -496,11 +496,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() @@ -526,8 +526,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: @@ -619,7 +619,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) @@ -747,7 +747,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) @@ -773,9 +773,10 @@ 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_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 @@ -791,8 +792,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 @@ -846,6 +859,7 @@ def usbRead(size: int, timeout: int = -1) -> bytes: except usb.core.USBError: if not g_cliMode: utilsLogException(traceback.format_exc()) + if g_logger is not None: g_logger.error('\nUSB timeout triggered or console disconnected.') @@ -859,13 +873,14 @@ def usbWrite(data: bytes, timeout: int = -1) -> int: except usb.core.USBError: if not g_cliMode: utilsLogException(traceback.format_exc()) + 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: @@ -1020,6 +1035,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) + printable_fullpath = (fullpath[4:] if g_isWindows else fullpath) printable_fullpath = (fullpath[4:] if g_isWindows else fullpath) @@ -1036,11 +1052,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'\nOutput filepath points to an existing directory! ("{printable_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('\nNot enough free space available in output volume!\n') @@ -1090,10 +1106,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): @@ -1117,8 +1130,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. @@ -1152,7 +1170,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() @@ -1259,8 +1277,7 @@ def usbHandleEndSession(cmd_block: bytes) -> int: def usbHandleStartExtractedFsDump(cmd_block: bytes) -> int: 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'Received StartExtractedFsDump ({USB_CMD_START_EXTRACTED_FS_DUMP:02X}) command.') if g_nspTransferMode: @@ -1310,7 +1327,6 @@ def usbHandleStartExtractedFsDump(cmd_block: bytes) -> int: return USB_STATUS_SUCCESS def usbHandleEndExtractedFsDump(cmd_block: bytes) -> int: - global g_extractedFsDumpMode, g_extractedFsAbsRoot 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.') @@ -1422,10 +1438,25 @@ 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() + assert g_tkDirText is not None + + g_outputDir = g_tkDirText.get('1.0', tk.END).strip() + if not g_outputDir: + # We should never reach this, honestly. + 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) + except: + utilsLogException(traceback.format_exc()) + messagebox.showerror('Error', 'Unable to create full output directory tree!', parent=g_tkRoot) + return # Update UI. uiToggleElements(False) @@ -1442,7 +1473,6 @@ def uiToggleElements(flag: bool) -> 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: @@ -1529,11 +1559,7 @@ def uiHandleLogToFileCheckbox() -> None: def uiHandleVerboseCheckbox() -> None: assert g_logger is not None assert g_logLevelIntVar is not None - global g_logVerbose - logLevel=g_logLevelIntVar.get() - g_logger.setLevel(logLevel) - g_logVerbose = True if(logLevel == logging.DEBUG) else False - return + g_logger.setLevel(g_logLevelIntVar.get()) def uiInitialize() -> None: global SCALE, g_logLevelIntVar, g_logToFileBoolVar, g_logToFile, g_logVerbose @@ -1678,6 +1704,8 @@ def uiInitialize() -> None: def cliInitialize() -> None: 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 @@ -1688,16 +1716,20 @@ def cliInitialize() -> None: # NB, g_outputDir should be adjusted for Windows prior. if g_logToFile: utilsUpdateLogPath() - # Initialize console logger. console = LogConsole() # Initialize progress bar window object. bar_format = '{percentage:.2f}% |{bar}| {n:.2f}/{total:.2f} [{elapsed}<{remaining}, {rate_fmt}]' g_progressBarWindow = ProgressBarWindow(bar_format) - - # Log basic info about the script and settings. - utilsLogBasicScriptInfo() + + # Print info. + 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() @@ -1709,12 +1741,11 @@ def main() -> int: warnings.filterwarnings("ignore") # Parse command line arguments. - parser = ArgumentParser(formatter_class=RawTextHelpFormatter, description=SCRIPT_TITLE + '. ' + COPYRIGHT_TEXT + '.') + parser = ArgumentParser(description=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; will attempt to create if non-existent.'+\ - '\nDefaults to "' + DEFAULT_DIR + '".') - parser.add_argument('-l', '--log', required=False, action='store_true', default=False, help='Enables logging to file in output directory in CLI mode.') + 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() # Update global flags. @@ -1766,9 +1797,7 @@ if __name__ == "__main__": ret = main() except KeyboardInterrupt: time.sleep(0.2) - g_logger.info("Host script exited!") - if g_isWindows10: print(COLOR_RESET) - + print('\nScript interrupted.') except: utilsLogException(traceback.format_exc()) 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 diff --git a/include/core/config.h b/include/core/config.h index 232dc0e..000364e 100644 --- a/include/core/config.h +++ b/include/core/config.h @@ -49,6 +49,9 @@ bool configInitialize(void); /// Closes the configuration interface. void configExit(void); +/// Resets settings to their default values. +void configResetSettings(void); + /// Getters and setters for various data types. /// Path elements must be separated using forward slashes. diff --git a/include/core/tik.h b/include/core/tik.h index 02d9e6a..4cb3107 100644 --- a/include/core/tik.h +++ b/include/core/tik.h @@ -33,6 +33,8 @@ extern "C" { #define SIGNED_TIK_MIN_SIZE sizeof(TikSigHmac160) /* Assuming no ESV1/ESV2 records are available. */ #define SIGNED_TIK_MAX_SIZE 0x400 /* Max ticket entry size in the ES ticket system savedata file. */ +#define TIK_FORMAT_VERSION 2 + #define GENERATE_TIK_STRUCT(sigtype, tiksize) \ typedef struct { \ SignatureBlock##sigtype sig_block; \ @@ -72,7 +74,7 @@ typedef enum { typedef struct { char issuer[0x40]; u8 titlekey_block[0x100]; - u8 format_version; + u8 format_version; ///< Always matches TIK_FORMAT_VERSION. u8 titlekey_type; ///< TikTitleKeyType. u16 ticket_version; u8 license_type; ///< TikLicenseType. diff --git a/include/defines.h b/include/defines.h index 1cfb74f..5b9c3b9 100644 --- a/include/defines.h +++ b/include/defines.h @@ -133,6 +133,4 @@ #define DISCORD_SERVER_URL "https://discord.gg/SCbbcQx" -#define LOCKPICK_RCM_URL "https://github.com/shchmue/Lockpick_RCM" - #endif /* __DEFINES_H__ */ diff --git a/source/core/config.c b/source/core/config.c index 93cbbb8..ed6a3ea 100644 --- a/source/core/config.c +++ b/source/core/config.c @@ -66,6 +66,7 @@ static struct json_object *g_configJson = NULL; /* Function prototypes. */ static bool configParseConfigJson(void); +static bool configResetConfigJson(void); static void configWriteConfigJson(void); static void configFreeConfigJson(void); @@ -111,6 +112,11 @@ void configExit(void) } } +void configResetSettings(void) +{ + configResetConfigJson(); +} + CONFIG_GETTER(Boolean, bool); CONFIG_SETTER(Boolean, bool); @@ -151,29 +157,36 @@ static bool configParseConfigJson(void) jsonLogLastError(); } - if (use_default_config) - { - LOG_MSG_INFO("Loading default configuration."); - - /* Free config JSON. */ - configFreeConfigJson(); - - /* Read default config JSON. */ - g_configJson = json_object_from_file(DEFAULT_CONFIG_PATH); - if (g_configJson) - { - configWriteConfigJson(); - ret = true; - } else { - jsonLogLastError(); - } - } + /* Try to load the default settings. */ + if (use_default_config) ret = configResetConfigJson(); if (!ret) LOG_MSG_ERROR("Failed to parse both current and default JSON configuration files!"); return ret; } +static bool configResetConfigJson(void) +{ + bool ret = false; + + LOG_MSG_INFO("Loading default configuration."); + + /* Free config JSON. */ + configFreeConfigJson(); + + /* Read default config JSON. */ + g_configJson = json_object_from_file(DEFAULT_CONFIG_PATH); + if (g_configJson) + { + configWriteConfigJson(); + ret = true; + } else { + jsonLogLastError(); + } + + return ret; +} + static void configWriteConfigJson(void) { if (!g_configJson) return; diff --git a/source/core/gamecard.c b/source/core/gamecard.c index 4494e7d..b0236b6 100644 --- a/source/core/gamecard.c +++ b/source/core/gamecard.c @@ -28,7 +28,7 @@ #define GAMECARD_READ_BUFFER_SIZE 0x800000 /* 8 MiB. */ -#define GAMECARD_ACCESS_DELAY 3 /* Seconds. */ +#define GAMECARD_ACCESS_DELAY 3 /* Seconds. */ #define GAMECARD_UNUSED_AREA_BLOCK_SIZE 0x24 #define GAMECARD_UNUSED_AREA_SIZE(x) (((x) / GAMECARD_PAGE_SIZE) * GAMECARD_UNUSED_AREA_BLOCK_SIZE) @@ -134,7 +134,6 @@ static bool _gamecardGetDecryptedCardInfoArea(void); static bool gamecardReadSecurityInformation(GameCardSecurityInformation *out); static bool gamecardGetHandleAndStorage(u32 partition); -NX_INLINE void gamecardCloseHandle(void); static bool gamecardOpenStorageArea(u8 area); static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset); @@ -259,6 +258,12 @@ void gamecardExit(void) g_gameCardReadBuf = NULL; } + /* Make sure NS can access the gamecard. */ + /* Fixes gamecard launch errors after exiting the application. */ + /* TODO: find out why this doesn't work. */ + //Result rc = nsEnsureGameCardAccess(); + //if (R_FAILED(rc)) LOG_MSG_ERROR("nsEnsureGameCardAccess failed! (0x%X).", rc); + g_gameCardInterfaceInit = false; } } @@ -866,7 +871,7 @@ static bool gamecardReadHeader(void) } /* Read gamecard header. */ - /* This step doesn't rely on gamecardReadStorageArea() because of its dependence on storage area sizes (which we haven't retrieved). */ + /* We don't use gamecardReadStorageArea() here because of its dependence on storage area sizes (which we haven't yet retrieved). */ Result rc = fsStorageRead(&g_gameCardStorage, 0, &g_gameCardHeader, sizeof(GameCardHeader)); if (R_FAILED(rc)) { @@ -1000,7 +1005,6 @@ static bool gamecardGetHandleAndStorage(u32 partition) rc = fsOpenGameCardStorage(&g_gameCardStorage, &g_gameCardHandle, partition); if (R_FAILED(rc)) { - gamecardCloseHandle(); /* Close invalid gamecard handle. */ LOG_MSG_DEBUG("fsOpenGameCardStorage failed to open %s storage area on try #%u! (0x%X).", GAMECARD_STORAGE_AREA_NAME(partition + 1), i + 1, rc); continue; } @@ -1018,11 +1022,6 @@ static bool gamecardGetHandleAndStorage(u32 partition) return R_SUCCEEDED(rc); } -NX_INLINE void gamecardCloseHandle(void) -{ - g_gameCardHandle.value = 0; -} - static bool gamecardOpenStorageArea(u8 area) { if (g_gameCardStatus < GameCardStatus_InsertedAndInfoNotLoaded || (area != GameCardStorageArea_Normal && area != GameCardStorageArea_Secure)) @@ -1034,7 +1033,7 @@ static bool gamecardOpenStorageArea(u8 area) /* Return right away if a valid handle has already been retrieved and the desired gamecard storage area is currently open. */ if (g_gameCardHandle.value && serviceIsActive(&(g_gameCardStorage.s)) && g_gameCardCurrentStorageArea == area) return true; - /* Close both gamecard handle and open storage area. */ + /* Close both the gamecard handle and the open storage area. */ gamecardCloseStorageArea(); /* Retrieve both a new gamecard handle and a storage area handle. */ @@ -1137,7 +1136,7 @@ static void gamecardCloseStorageArea(void) memset(&g_gameCardStorage, 0, sizeof(FsStorage)); } - gamecardCloseHandle(); + g_gameCardHandle.value = 0; g_gameCardCurrentStorageArea = GameCardStorageArea_None; } diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index 1db1595..9d16011 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -202,7 +202,7 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv) /* Load keyset. */ if (!keysLoadKeyset()) { - LOG_MSG_ERROR("Failed to load keyset!\nUpdate your keys file with Lockpick_RCM:\n" LOCKPICK_RCM_URL); + LOG_MSG_ERROR("Failed to load keyset!\nPlease update your keys file with Lockpick_RCM.\nYou can get an updated build at:" DISCORD_SERVER_URL); break; } diff --git a/source/core/tik.c b/source/core/tik.c index ff8b4a1..6bd2dfa 100644 --- a/source/core/tik.c +++ b/source/core/tik.c @@ -386,8 +386,9 @@ static bool tikFixTamperedCommonTicket(Ticket *tik) TikCommonBlock *tik_common_block = NULL; u32 sig_type = 0; - u8 *signature = NULL; - u64 signature_size = 0, hash_area_size = 0; + bool dev_cert = false; + TikSigRsa2048 *tik_data = NULL; + u64 hash_area_size = 0; bool success = false; @@ -397,15 +398,23 @@ static bool tikFixTamperedCommonTicket(Ticket *tik) return false; } - /* Get ticket signature and its properties, as well as the ticket hash area size. */ + /* Get ticket signature type. Also determine if it's a development ticket. */ sig_type = signatureGetTypeFromSignedBlob(tik->data, false); - signature = signatureGetSigFromSignedBlob(tik->data); - signature_size = signatureGetSigSizeByType(sig_type); + dev_cert = (strstr(tik_common_block->issuer, TIK_DEV_CERT_ISSUER) != NULL); + + /* Return right away if we're not dealing with a common ticket or if the signature type doesn't match RSA-2048 + SHA-256. */ + if (tik_common_block->titlekey_type != TikTitleKeyType_Common || sig_type != SignatureType_Rsa2048Sha256) + { + success = true; + goto end; + } + + /* Make sure we're dealing with a tampered ticket by verifying its signature. */ + tik_data = (TikSigRsa2048*)tik->data; + tik_common_block = &(tik_data->tik_common_block); hash_area_size = tikGetSignedTicketBlobHashAreaSize(tik->data); - /* Return right away if we're not dealing with a common ticket, if the signature type doesn't match RSA-2048 + SHA-256, or if the signature is valid. */ - if (tik_common_block->titlekey_type != TikTitleKeyType_Common || sig_type != SignatureType_Rsa2048Sha256 || \ - tikVerifyRsa2048Sha256Signature(tik_common_block, hash_area_size, signature)) + if (tikVerifyRsa2048Sha256Signature(tik_common_block, hash_area_size, tik_data->sig_block.signature)) { success = true; goto end; @@ -416,22 +425,35 @@ static bool tikFixTamperedCommonTicket(Ticket *tik) /* Nintendo didn't start putting the key generation value into the rights ID until HOS 3.0.1. */ /* Old custom tools used to wipe the key generation field and/or save its value into a different offset. */ /* We're gonna take care of that by setting the correct values where they need to go. */ - memset(signature, 0xFF, signature_size); + memset(tik_data->sig_block.signature, 0xFF, sizeof(tik_data->sig_block.signature)); + memset(tik_data->sig_block.padding, 0, sizeof(tik_data->sig_block.padding)); + memset(tik_common_block->issuer, 0, sizeof(tik_common_block->issuer)); + sprintf(tik_common_block->issuer, "Root-CA%08X-%s", dev_cert ? 4 : 3, TIK_COMMON_CERT_NAME); + + memset(tik_common_block->titlekey_block + 0x10, 0, sizeof(tik_common_block->titlekey_block) - 0x10); + + tik_common_block->format_version = TIK_FORMAT_VERSION; tik_common_block->titlekey_type = TikTitleKeyType_Common; + tik_common_block->ticket_version = 0; tik_common_block->license_type = TikLicenseType_Permanent; tik_common_block->key_generation = tik->key_generation; tik_common_block->property_mask = TikPropertyMask_None; + memset(tik_common_block->reserved, 0, sizeof(tik_common_block->reserved)); + tik_common_block->ticket_id = 0; tik_common_block->device_id = 0; tik_common_block->account_id = 0; tik_common_block->sect_total_size = 0; - tik_common_block->sect_hdr_offset = (u32)tik->size; + tik_common_block->sect_hdr_offset = (u32)sizeof(TikSigRsa2048); tik_common_block->sect_hdr_count = 0; tik_common_block->sect_hdr_entry_size = 0; + /* Update ticket size. */ + tik->size = sizeof(TikSigRsa2048); + /* Update return value. */ success = true; @@ -447,8 +469,8 @@ static bool tikVerifyRsa2048Sha256Signature(const TikCommonBlock *tik_common_blo return false; } - const char *cert_name = (strrchr(tik_common_block->issuer, '-') + 1); Certificate cert = {0}; + const char *cert_name = (strrchr(tik_common_block->issuer, '-') + 1); const u8 *modulus = NULL, *public_exponent = NULL; /* Get certificate for the ticket signature issuer. */ diff --git a/source/core/title.c b/source/core/title.c index aa0e871..9c9affd 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -552,7 +552,7 @@ static bool titleRetrieveUserApplicationMetadataByTitleId(u64 title_id, TitleApp NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id, bool is_system, u32 extra_app_count); -NX_INLINE u64 titleGetApplicationIdByMetaKey(const NcmContentMetaKey *meta_key); +NX_INLINE u64 titleGetApplicationIdByContentMetaKey(const NcmContentMetaKey *meta_key); static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_storage); static bool titleGetMetaKeysFromContentDatabase(NcmContentMetaDatabase *ncm_db, NcmContentMetaKey **out_meta_keys, u32 *out_meta_key_count); @@ -1026,6 +1026,7 @@ TitleInfo **titleGetOrphanTitles(u32 *out_count) } /* Allocate orphan title info pointer array. */ + /* titleFreeOrphanTitles() depends on the last NULL element. */ orphan_info = calloc(g_orphanTitleInfoCount + 1, sizeof(TitleInfo*)); if (!orphan_info) { @@ -1373,6 +1374,33 @@ NX_INLINE bool titleInitializePersistentTitleStorages(void) } } +#if LOG_LEVEL <= LOG_LEVEL_INFO +#define ORPHAN_INFO_LOG(fmt, ...) utilsAppendFormattedStringToBuffer(&orphan_info_buf, &orphan_info_buf_size, fmt, ##__VA_ARGS__) + + if (g_orphanTitleInfo && g_orphanTitleInfoCount) + { + char *orphan_info_buf = NULL; + size_t orphan_info_buf_size = 0; + + ORPHAN_INFO_LOG("Identified %u orphan title(s) across all initialized title storages.\r\n", g_orphanTitleInfoCount); + + for(u32 i = 0; i < g_orphanTitleInfoCount; i++) + { + TitleInfo *orphan_info = g_orphanTitleInfo[i]; + ORPHAN_INFO_LOG("- %016lX v%u (%s, %s).%s", orphan_info->meta_key.id, orphan_info->version.value, titleGetNcmContentMetaTypeName(orphan_info->meta_key.type), \ + titleGetNcmStorageIdName(orphan_info->storage_id), (i + 1) < g_orphanTitleInfoCount ? "\r\n" : ""); + } + + if (orphan_info_buf) + { + LOG_MSG_INFO("%s", orphan_info_buf); + free(orphan_info_buf); + } + } + +#undef ORPHAN_INFO_LOG +#endif /* LOG_LEVEL <= LOG_LEVEL_INFO */ + return true; } @@ -1861,7 +1889,7 @@ NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 ti return NULL; } -NX_INLINE u64 titleGetApplicationIdByMetaKey(const NcmContentMetaKey *meta_key) +NX_INLINE u64 titleGetApplicationIdByContentMetaKey(const NcmContentMetaKey *meta_key) { if (!meta_key) return 0; @@ -1961,7 +1989,7 @@ static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_sto utilsGenerateFormattedSizeString((double)cur_title_info->size, cur_title_info->size_str, sizeof(cur_title_info->size_str)); /* Retrieve application metadata. */ - u64 app_id = titleGetApplicationIdByMetaKey(&(cur_title_info->meta_key)); + u64 app_id = titleGetApplicationIdByContentMetaKey(&(cur_title_info->meta_key)); cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, storage_id == NcmStorageId_BuiltInSystem, 0); if (!cur_title_info->app_metadata && storage_id == NcmStorageId_BuiltInSystem) { @@ -2203,7 +2231,7 @@ static void titleUpdateTitleInfoLinkedLists(void) { /* We're dealing with a patch, an add-on content or an add-on content patch. */ /* We'll just retrieve a pointer to the first matching user application entry and use it to set a pointer to an application metadata entry. */ - u64 app_id = titleGetApplicationIdByMetaKey(&(child_info->meta_key)); + u64 app_id = titleGetApplicationIdByContentMetaKey(&(child_info->meta_key)); TitleInfo *parent = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id); if (parent) { @@ -2367,7 +2395,7 @@ static bool titleRefreshGameCardTitleInfo(void) if (!cur_title_info) continue; /* Do not proceed if application metadata has already been retrieved, or if we can successfully retrieve it. */ - u64 app_id = titleGetApplicationIdByMetaKey(&(cur_title_info->meta_key)); + u64 app_id = titleGetApplicationIdByContentMetaKey(&(cur_title_info->meta_key)); if (cur_title_info->app_metadata != NULL || (cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, false, extra_app_count)) != NULL) continue; /* Retrieve application metadata pointer. */ diff --git a/source/core/usb.c b/source/core/usb.c index b6e4340..cd788a1 100644 --- a/source/core/usb.c +++ b/source/core/usb.c @@ -160,7 +160,7 @@ enum usb_supported_speed { }; /// Imported from libusb, with some adjustments. -struct PACKED usb_bos_descriptor { +struct NX_PACKED usb_bos_descriptor { u8 bLength; u8 bDescriptorType; ///< Must match USB_DT_BOS. u16 wTotalLength; ///< Length of this descriptor and all of its sub descriptors. @@ -170,7 +170,7 @@ struct PACKED usb_bos_descriptor { NXDT_ASSERT(struct usb_bos_descriptor, 0x5); /// Imported from libusb, with some adjustments. -struct PACKED usb_2_0_extension_descriptor { +struct NX_PACKED usb_2_0_extension_descriptor { u8 bLength; u8 bDescriptorType; ///< Must match USB_DT_DEVICE_CAPABILITY. u8 bDevCapabilityType; ///< Must match USB_BT_USB_2_0_EXTENSION. @@ -180,7 +180,7 @@ struct PACKED usb_2_0_extension_descriptor { NXDT_ASSERT(struct usb_2_0_extension_descriptor, 0x7); /// Imported from libusb, with some adjustments. -struct PACKED usb_ss_usb_device_capability_descriptor { +struct NX_PACKED usb_ss_usb_device_capability_descriptor { u8 bLength; u8 bDescriptorType; ///< Must match USB_DT_DEVICE_CAPABILITY. u8 bDevCapabilityType; ///< Must match USB_BT_SS_USB_DEVICE_CAPABILITY. diff --git a/source/exception_handler.cpp b/source/exception_handler.cpp index 00fa423..71f0f5e 100644 --- a/source/exception_handler.cpp +++ b/source/exception_handler.cpp @@ -92,7 +92,7 @@ namespace nxdt::utils { } #endif /* LOG_LEVEL < LOG_LEVEL_NONE */ - static void NORETURN AbortProgramExecution(std::string str) + static void NX_NORETURN AbortProgramExecution(std::string str) { if (g_borealisInitialized) {