mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-23 02:36:41 +00:00
Merging attempt; do not run
This commit is contained in:
commit
62fd26b622
15 changed files with 329 additions and 155 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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')
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue