mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-12-23 09:02:06 +00:00
New template: threaded gamecard dumper via USB.
Key area and certificate can be optionally appended and/or preserved, respectively. Trimming is supported. Also added a bunch of ZLT packet related fixes to usb.c, as well as a function to properly cancel an ongoing file transfer.
This commit is contained in:
parent
e26637125b
commit
ad401d559d
7 changed files with 1391 additions and 470 deletions
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* main.c
|
||||
*
|
||||
* Copyright (c) 2020, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||
*
|
||||
* nxdumptool is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* nxdumptool is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "utils.h"
|
||||
#include "gamecard.h"
|
||||
|
||||
static void consolePrint(const char *text, ...)
|
||||
{
|
||||
va_list v;
|
||||
va_start(v, text);
|
||||
vfprintf(stdout, text, v);
|
||||
va_end(v);
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
int ret = 0;
|
||||
|
||||
GameCardKeyArea gc_key_area = {0};
|
||||
char path[FS_MAX_PATH] = {0};
|
||||
|
||||
LOGFILE("nxdumptool starting.");
|
||||
|
||||
consoleInit(NULL);
|
||||
|
||||
consolePrint("initializing...\n");
|
||||
|
||||
if (!utilsInitializeResources())
|
||||
{
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
consolePrint("waiting for gamecard. press b to cancel.\n");
|
||||
|
||||
while(true)
|
||||
{
|
||||
hidScanInput();
|
||||
|
||||
if (utilsHidKeysAllDown() == KEY_B)
|
||||
{
|
||||
consolePrint("process cancelled\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
u8 status = gamecardGetStatus();
|
||||
if (status == GameCardStatus_InsertedAndInfoLoaded) break;
|
||||
if (status == GameCardStatus_InsertedAndInfoNotLoaded) consolePrint("gamecard inserted but info couldn't be loaded from it. check nogc patch setting.\n");
|
||||
}
|
||||
|
||||
utilsChangeHomeButtonBlockStatus(true);
|
||||
|
||||
consolePrint("gamecard detected.\n");
|
||||
|
||||
if (!gamecardGetKeyArea(&gc_key_area))
|
||||
{
|
||||
consolePrint("failed to get gamecard key area\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
consolePrint("get gamecard key area ok\n");
|
||||
|
||||
sprintf(path, "sdmc:/card_key_area_%016lX.bin", gc_key_area.initial_data.key_source.package_id);
|
||||
|
||||
FILE *kafd = fopen(path, "wb");
|
||||
if (!kafd)
|
||||
{
|
||||
consolePrint("failed to open \"%s\" for writing\n", path);
|
||||
goto out2;
|
||||
}
|
||||
|
||||
fwrite(&gc_key_area, 1, sizeof(GameCardKeyArea), kafd);
|
||||
fclose(kafd);
|
||||
utilsCommitSdCardFileSystemChanges();
|
||||
|
||||
consolePrint("successfully saved key area to \"%s\"\n", path);
|
||||
|
||||
utilsChangeHomeButtonBlockStatus(false);
|
||||
|
||||
out2:
|
||||
consolePrint("press any button to exit\n");
|
||||
utilsWaitForButtonPress(KEY_NONE);
|
||||
|
||||
out:
|
||||
utilsCloseResources();
|
||||
|
||||
consoleExit(NULL);
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -457,6 +457,7 @@ int main(int argc, char *argv[])
|
|||
if ((btn_cancel_end_tmr - btn_cancel_start_tmr) >= 3)
|
||||
{
|
||||
mutexLock(&g_fileMutex);
|
||||
usbCancelFileTransfer();
|
||||
shared_data.transfer_cancelled = true;
|
||||
mutexUnlock(&g_fileMutex);
|
||||
break;
|
||||
|
|
704
code_templates/threaded_usb_gc_dumper.c
Normal file
704
code_templates/threaded_usb_gc_dumper.c
Normal file
|
@ -0,0 +1,704 @@
|
|||
/*
|
||||
* main.c
|
||||
*
|
||||
* Copyright (c) 2020, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||
*
|
||||
* nxdumptool is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* nxdumptool is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "utils.h"
|
||||
#include "gamecard.h"
|
||||
#include "usb.h"
|
||||
|
||||
#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE
|
||||
|
||||
/* Type definitions. */
|
||||
|
||||
typedef void (*MenuElementOptionFunction)(u32 idx);
|
||||
|
||||
typedef struct {
|
||||
u32 selected; ///< Used to keep track of the selected option.
|
||||
MenuElementOptionFunction options_func; ///< Pointer to a function to be called each time a new option is selected. Should be set to NULL if not used.
|
||||
const char **options; ///< Pointer to multiple char pointers with strings representing options. Last element must be set to NULL.
|
||||
} MenuElementOption;
|
||||
|
||||
typedef bool (*MenuElementFunction)(void);
|
||||
|
||||
typedef struct {
|
||||
const char *str; ///< Pointer to a string to be printed for this menu element.
|
||||
void *child_menu; ///< Pointer to a child Menu element. Must be set to NULL if task_func != NULL.
|
||||
MenuElementFunction task_func; ///< Pointer to a function to be called by this element. Must be set to NULL if child_menu != NULL.
|
||||
MenuElementOption *element_options; ///< Options for this menu element. Should be set to NULL if not used.
|
||||
} MenuElement;
|
||||
|
||||
typedef struct _Menu {
|
||||
struct _Menu *parent; ///< Set to NULL in the root menu element.
|
||||
u32 selected, scroll; ///< Used to keep track of the selected element and scroll values.
|
||||
MenuElement **elements; ///< Element info from this menu. Last element must be set to NULL.
|
||||
} Menu;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
void *data;
|
||||
size_t data_size;
|
||||
size_t data_written;
|
||||
size_t total_size;
|
||||
bool read_error;
|
||||
bool write_error;
|
||||
bool transfer_cancelled;
|
||||
} ThreadSharedData;
|
||||
|
||||
/* Function prototypes. */
|
||||
|
||||
static void consolePrint(const char *text, ...);
|
||||
|
||||
static u32 menuGetElementCount(const Menu *menu);
|
||||
|
||||
static bool sendGameCardKeyAreaViaUsb(void);
|
||||
static bool sendGameCardCertificateViaUsb(void);
|
||||
static bool sendGameCardImageViaUsb(void);
|
||||
|
||||
static void changeKeyAreaOption(u32 idx);
|
||||
static void changeCertificateOption(u32 idx);
|
||||
static void changeTrimOption(u32 idx);
|
||||
|
||||
static int read_thread_func(void *arg);
|
||||
static int write_thread_func(void *arg);
|
||||
|
||||
/* Global variables. */
|
||||
|
||||
static bool g_appendKeyArea = false, g_keepCertificate = false, g_trimDump = false;
|
||||
|
||||
static const char *g_xciOptions[] = { "no", "yes", NULL };
|
||||
|
||||
static MenuElement *g_xciMenuElements[] = {
|
||||
&(MenuElement){
|
||||
.str = "start dump",
|
||||
.child_menu = NULL,
|
||||
.task_func = &sendGameCardImageViaUsb,
|
||||
.element_options = NULL
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "append key area",
|
||||
.child_menu = NULL,
|
||||
.task_func = NULL,
|
||||
.element_options = &(MenuElementOption){
|
||||
.selected = 0,
|
||||
.options_func = &changeKeyAreaOption,
|
||||
.options = g_xciOptions
|
||||
}
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "keep certificate",
|
||||
.child_menu = NULL,
|
||||
.task_func = NULL,
|
||||
.element_options = &(MenuElementOption){
|
||||
.selected = 0,
|
||||
.options_func = &changeCertificateOption,
|
||||
.options = g_xciOptions
|
||||
}
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "trim dump",
|
||||
.child_menu = NULL,
|
||||
.task_func = NULL,
|
||||
.element_options = &(MenuElementOption){
|
||||
.selected = 0,
|
||||
.options_func = &changeTrimOption,
|
||||
.options = g_xciOptions
|
||||
}
|
||||
},
|
||||
NULL
|
||||
};
|
||||
|
||||
static Menu g_xciMenu = {
|
||||
.parent = NULL,
|
||||
.selected = 0,
|
||||
.scroll = 0,
|
||||
.elements = g_xciMenuElements
|
||||
};
|
||||
|
||||
static MenuElement *g_rootMenuElements[] = {
|
||||
&(MenuElement){
|
||||
.str = "dump key area",
|
||||
.child_menu = NULL,
|
||||
.task_func = &sendGameCardKeyAreaViaUsb,
|
||||
.element_options = NULL
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "dump certificate",
|
||||
.child_menu = NULL,
|
||||
.task_func = &sendGameCardCertificateViaUsb,
|
||||
.element_options = NULL
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "dump xci",
|
||||
.child_menu = &g_xciMenu,
|
||||
.task_func = NULL,
|
||||
.element_options = NULL
|
||||
},
|
||||
NULL
|
||||
};
|
||||
|
||||
static Menu g_rootMenu = {
|
||||
.parent = NULL,
|
||||
.selected = 0,
|
||||
.scroll = 0,
|
||||
.elements = g_rootMenuElements
|
||||
};
|
||||
|
||||
static Mutex g_fileMutex = 0;
|
||||
static CondVar g_readCondvar = 0, g_writeCondvar = 0;
|
||||
|
||||
static char path[FS_MAX_PATH] = {0};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
int ret = 0;
|
||||
|
||||
Menu *cur_menu = &g_rootMenu;
|
||||
u32 element_count = menuGetElementCount(cur_menu), page_size = 30;
|
||||
|
||||
LOGFILE(APP_TITLE " starting.");
|
||||
|
||||
consoleInit(NULL);
|
||||
|
||||
consolePrint("initializing...\n");
|
||||
|
||||
if (!utilsInitializeResources())
|
||||
{
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
while(appletMainLoop())
|
||||
{
|
||||
consoleClear();
|
||||
printf("\npress b to %s.\n\n", cur_menu->parent ? "go back" : "exit");
|
||||
|
||||
u32 limit = (cur_menu->scroll + page_size);
|
||||
MenuElement *selected_element = cur_menu->elements[cur_menu->selected];
|
||||
MenuElementOption *selected_element_options = selected_element->element_options;
|
||||
|
||||
for(u32 i = cur_menu->scroll; cur_menu->elements[i] && i < element_count; i++)
|
||||
{
|
||||
if (i >= limit) break;
|
||||
|
||||
MenuElement *cur_element = cur_menu->elements[i];
|
||||
MenuElementOption *cur_options = cur_menu->elements[i]->element_options;
|
||||
|
||||
printf("%s%s", i == cur_menu->selected ? " -> " : " ", cur_element->str);
|
||||
|
||||
if (cur_options)
|
||||
{
|
||||
printf(": ");
|
||||
if (cur_options->selected > 0) printf("< ");
|
||||
printf("%s", cur_options->options[cur_options->selected]);
|
||||
if (cur_options->options[cur_options->selected + 1]) printf(" >");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
u64 btn_down = 0, btn_held = 0;
|
||||
while(!btn_down && !btn_held)
|
||||
{
|
||||
hidScanInput();
|
||||
btn_down = utilsHidKeysAllDown();
|
||||
btn_held = utilsHidKeysAllHeld();
|
||||
}
|
||||
|
||||
if (btn_down & KEY_A)
|
||||
{
|
||||
Menu *child_menu = (Menu*)selected_element->child_menu;
|
||||
|
||||
if (child_menu)
|
||||
{
|
||||
child_menu->parent = cur_menu;
|
||||
cur_menu = child_menu;
|
||||
element_count = menuGetElementCount(cur_menu);
|
||||
} else
|
||||
if (selected_element->task_func)
|
||||
{
|
||||
selected_element->task_func();
|
||||
}
|
||||
} else
|
||||
if ((btn_down & KEY_DDOWN) || (btn_held & (KEY_LSTICK_DOWN | KEY_RSTICK_DOWN)))
|
||||
{
|
||||
cur_menu->selected++;
|
||||
|
||||
if (!cur_menu->elements[cur_menu->selected])
|
||||
{
|
||||
if (btn_down & KEY_DDOWN)
|
||||
{
|
||||
cur_menu->selected = 0;
|
||||
cur_menu->scroll = 0;
|
||||
} else {
|
||||
cur_menu->selected--;
|
||||
}
|
||||
} else
|
||||
if (cur_menu->selected >= limit && cur_menu->elements[cur_menu->selected + 1])
|
||||
{
|
||||
cur_menu->scroll++;
|
||||
}
|
||||
} else
|
||||
if ((btn_down & KEY_DUP) || (btn_held & (KEY_LSTICK_UP | KEY_RSTICK_UP)))
|
||||
{
|
||||
cur_menu->selected--;
|
||||
|
||||
if (cur_menu->selected == UINT32_MAX)
|
||||
{
|
||||
if (btn_down & KEY_DUP)
|
||||
{
|
||||
cur_menu->selected = (element_count - 1);
|
||||
cur_menu->scroll = (element_count > page_size ? (element_count - page_size) : 0);
|
||||
} else {
|
||||
cur_menu->selected = 0;
|
||||
}
|
||||
} else
|
||||
if (cur_menu->selected < cur_menu->scroll && cur_menu->scroll > 0)
|
||||
{
|
||||
cur_menu->scroll--;
|
||||
}
|
||||
} else
|
||||
if ((btn_down & (KEY_DRIGHT | KEY_LSTICK_RIGHT | KEY_RSTICK_RIGHT)) && selected_element_options)
|
||||
{
|
||||
selected_element_options->selected++;
|
||||
if (!selected_element_options->options[selected_element_options->selected]) selected_element_options->selected--;
|
||||
if (selected_element_options->options_func) selected_element_options->options_func(selected_element_options->selected);
|
||||
} else
|
||||
if ((btn_down & (KEY_DLEFT | KEY_LSTICK_LEFT | KEY_RSTICK_LEFT)) && selected_element_options)
|
||||
{
|
||||
selected_element_options->selected--;
|
||||
if (selected_element_options->selected == UINT32_MAX) selected_element_options->selected = 0;
|
||||
if (selected_element_options->options_func) selected_element_options->options_func(selected_element_options->selected);
|
||||
} else
|
||||
if (btn_down & KEY_B)
|
||||
{
|
||||
if (!cur_menu->parent) break;
|
||||
|
||||
cur_menu = cur_menu->parent;
|
||||
element_count = menuGetElementCount(cur_menu);
|
||||
}
|
||||
|
||||
if (btn_held & (KEY_LSTICK_DOWN | KEY_RSTICK_DOWN | KEY_LSTICK_UP | KEY_RSTICK_UP)) svcSleepThread(50000000); // 50 ms
|
||||
}
|
||||
|
||||
out:
|
||||
utilsCloseResources();
|
||||
|
||||
consoleExit(NULL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void consolePrint(const char *text, ...)
|
||||
{
|
||||
va_list v;
|
||||
va_start(v, text);
|
||||
vfprintf(stdout, text, v);
|
||||
va_end(v);
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
static u32 menuGetElementCount(const Menu *menu)
|
||||
{
|
||||
if (!menu || !menu->elements || !menu->elements[0]) return 0;
|
||||
|
||||
u32 cnt;
|
||||
for(cnt = 0; menu->elements[cnt]; cnt++);
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static void waitForGameCardAndUsb(void)
|
||||
{
|
||||
consoleClear();
|
||||
consolePrint("waiting for gamecard and usb session...\n");
|
||||
|
||||
while(true)
|
||||
{
|
||||
if (gamecardGetStatus() == GameCardStatus_InsertedAndInfoLoaded && usbIsReady()) break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool sendFileData(const char *path, void *data, size_t data_size)
|
||||
{
|
||||
if (!path || !strlen(path) || !data || !data_size)
|
||||
{
|
||||
consolePrint("invalid parameters to send file data!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!usbSendFileProperties(data_size, path))
|
||||
{
|
||||
consolePrint("failed to send file properties for \"%s\"!\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!usbSendFileData(data, data_size))
|
||||
{
|
||||
consolePrint("failed to send file data for \"%s\"!\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool dumpGameCardKeyArea(GameCardKeyArea *out)
|
||||
{
|
||||
if (!out)
|
||||
{
|
||||
consolePrint("invalid parameters to dump key area!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gamecardGetKeyArea(out))
|
||||
{
|
||||
consolePrint("failed to get gamecard key area\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
consolePrint("get gamecard key area ok\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sendGameCardKeyAreaViaUsb(void)
|
||||
{
|
||||
waitForGameCardAndUsb();
|
||||
|
||||
utilsChangeHomeButtonBlockStatus(false);
|
||||
|
||||
GameCardKeyArea gc_key_area = {0};
|
||||
bool success = false;
|
||||
|
||||
if (!dumpGameCardKeyArea(&gc_key_area)) goto end;
|
||||
|
||||
sprintf(path, "card_key_area_%016lX.bin", gc_key_area.initial_data.key_source.package_id);
|
||||
if (!sendFileData(path, &gc_key_area, sizeof(GameCardKeyArea))) goto end;
|
||||
|
||||
consolePrint("successfully sent key area as \"%s\"\n", path);
|
||||
success = true;
|
||||
|
||||
end:
|
||||
utilsChangeHomeButtonBlockStatus(false);
|
||||
|
||||
consolePrint("press any button to continue");
|
||||
utilsWaitForButtonPress(KEY_NONE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool sendGameCardCertificateViaUsb(void)
|
||||
{
|
||||
waitForGameCardAndUsb();
|
||||
|
||||
utilsChangeHomeButtonBlockStatus(true);
|
||||
|
||||
FsGameCardCertificate gc_cert = {0};
|
||||
char device_id_str[0x21] = {0};
|
||||
bool success = false;
|
||||
|
||||
if (!gamecardGetCertificate(&gc_cert))
|
||||
{
|
||||
consolePrint("failed to get gamecard certificate\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
consolePrint("get gamecard certificate ok\n");
|
||||
|
||||
utilsGenerateHexStringFromData(device_id_str, 0x21, gc_cert.device_id, 0x10);
|
||||
sprintf(path, "card_certificate_%s.bin", device_id_str);
|
||||
if (!sendFileData(path, &gc_cert, sizeof(FsGameCardCertificate))) goto end;
|
||||
|
||||
consolePrint("successfully sent certificate as \"%s\"\n", path);
|
||||
success = true;
|
||||
|
||||
end:
|
||||
utilsChangeHomeButtonBlockStatus(false);
|
||||
|
||||
consolePrint("press any button to continue");
|
||||
utilsWaitForButtonPress(KEY_NONE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool sendGameCardImageViaUsb(void)
|
||||
{
|
||||
waitForGameCardAndUsb();
|
||||
|
||||
utilsChangeHomeButtonBlockStatus(true);
|
||||
|
||||
u64 gc_size = 0;
|
||||
GameCardKeyArea gc_key_area = {0};
|
||||
|
||||
ThreadSharedData shared_data = {0};
|
||||
thrd_t read_thread, write_thread;
|
||||
|
||||
bool success = false;
|
||||
|
||||
consolePrint("gamecard image dump\nappend key area: %s | keep certificate: %s | trim dump: %s\n\n", g_appendKeyArea ? "yes" : "no", g_keepCertificate ? "yes" : "no", g_trimDump ? "yes" : "no");
|
||||
|
||||
shared_data.data = usbAllocatePageAlignedBuffer(BLOCK_SIZE);
|
||||
if (!shared_data.data)
|
||||
{
|
||||
consolePrint("failed to allocate memory for the dump procedure!\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
if ((!g_trimDump && !gamecardGetTotalSize(&gc_size)) || (g_trimDump && !gamecardGetTrimmedSize(&gc_size)) || !gc_size)
|
||||
{
|
||||
consolePrint("failed to get gamecard size!\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
shared_data.total_size = gc_size;
|
||||
|
||||
consolePrint("gamecard size: 0x%lX\n", gc_size);
|
||||
|
||||
if (g_appendKeyArea)
|
||||
{
|
||||
gc_size += sizeof(GameCardKeyArea);
|
||||
if (!dumpGameCardKeyArea(&gc_key_area)) goto end;
|
||||
consolePrint("gamecard size (with key area): 0x%lX\n", gc_size);
|
||||
}
|
||||
|
||||
sprintf(path, "gamecard (%s) (%s) (%s).xci", g_appendKeyArea ? "keyarea" : "keyarealess", g_keepCertificate ? "cert" : "certless", g_trimDump ? "trimmed" : "untrimmed");
|
||||
if (!usbSendFileProperties(gc_size, path))
|
||||
{
|
||||
consolePrint("failed to send file properties for \"%s\"!\n", path);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (g_appendKeyArea && !usbSendFileData(&gc_key_area, sizeof(GameCardKeyArea)))
|
||||
{
|
||||
consolePrint("failed to send gamecard key area data!\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
consolePrint("creating threads\n");
|
||||
thrd_create(&read_thread, read_thread_func, &shared_data);
|
||||
thrd_create(&write_thread, write_thread_func, &shared_data);
|
||||
|
||||
u8 prev_time = 0;
|
||||
u64 prev_size = 0;
|
||||
u8 percent = 0;
|
||||
|
||||
time_t start = 0, btn_cancel_start_tmr = 0, btn_cancel_end_tmr = 0;
|
||||
bool btn_cancel_cur_state = false, btn_cancel_prev_state = false;
|
||||
|
||||
consolePrint("hold b to cancel\n\n");
|
||||
|
||||
start = time(NULL);
|
||||
|
||||
while(shared_data.data_written < shared_data.total_size)
|
||||
{
|
||||
if (shared_data.read_error || shared_data.write_error) break;
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct tm *ts = localtime(&now);
|
||||
size_t size = shared_data.data_written;
|
||||
|
||||
hidScanInput();
|
||||
btn_cancel_cur_state = (utilsHidKeysAllHeld() & KEY_B);
|
||||
|
||||
if (btn_cancel_cur_state && btn_cancel_cur_state != btn_cancel_prev_state)
|
||||
{
|
||||
btn_cancel_start_tmr = now;
|
||||
} else
|
||||
if (btn_cancel_cur_state && btn_cancel_cur_state == btn_cancel_prev_state)
|
||||
{
|
||||
btn_cancel_end_tmr = now;
|
||||
if ((btn_cancel_end_tmr - btn_cancel_start_tmr) >= 3)
|
||||
{
|
||||
mutexLock(&g_fileMutex);
|
||||
usbCancelFileTransfer();
|
||||
shared_data.transfer_cancelled = true;
|
||||
mutexUnlock(&g_fileMutex);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
btn_cancel_start_tmr = btn_cancel_end_tmr = 0;
|
||||
}
|
||||
|
||||
btn_cancel_prev_state = btn_cancel_cur_state;
|
||||
|
||||
if (prev_time == ts->tm_sec || prev_size == size) continue;
|
||||
|
||||
percent = (u8)((size * 100) / shared_data.total_size);
|
||||
|
||||
prev_time = ts->tm_sec;
|
||||
prev_size = size;
|
||||
|
||||
printf("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_data.total_size, percent, (now - start));
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
start = (time(NULL) - start);
|
||||
|
||||
consolePrint("\nwaiting for threads to join\n");
|
||||
thrd_join(read_thread, NULL);
|
||||
consolePrint("read_thread done: %lu\n", time(NULL));
|
||||
thrd_join(write_thread, NULL);
|
||||
consolePrint("write_thread done: %lu\n", time(NULL));
|
||||
|
||||
if (shared_data.read_error || shared_data.write_error)
|
||||
{
|
||||
consolePrint("usb transfer error\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (shared_data.transfer_cancelled)
|
||||
{
|
||||
consolePrint("process cancelled\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
consolePrint("process completed in %lu seconds\n", start);
|
||||
success = true;
|
||||
|
||||
end:
|
||||
if (shared_data.data) free(shared_data.data);
|
||||
|
||||
utilsChangeHomeButtonBlockStatus(false);
|
||||
|
||||
consolePrint("press any button to continue");
|
||||
utilsWaitForButtonPress(KEY_NONE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static void changeKeyAreaOption(u32 idx)
|
||||
{
|
||||
g_appendKeyArea = (idx > 0);
|
||||
}
|
||||
|
||||
static void changeCertificateOption(u32 idx)
|
||||
{
|
||||
g_keepCertificate = (idx > 0);
|
||||
}
|
||||
|
||||
static void changeTrimOption(u32 idx)
|
||||
{
|
||||
g_trimDump = (idx > 0);
|
||||
}
|
||||
|
||||
static int read_thread_func(void *arg)
|
||||
{
|
||||
ThreadSharedData *shared_data = (ThreadSharedData*)arg;
|
||||
if (!shared_data || !shared_data->data || !shared_data->total_size)
|
||||
{
|
||||
shared_data->read_error = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
u8 *buf = malloc(BLOCK_SIZE);
|
||||
if (!buf)
|
||||
{
|
||||
shared_data->read_error = true;
|
||||
return -2;
|
||||
}
|
||||
|
||||
for(u64 offset = 0, blksize = BLOCK_SIZE; offset < shared_data->total_size; offset += blksize)
|
||||
{
|
||||
if (blksize > (shared_data->total_size - offset)) blksize = (shared_data->total_size - offset);
|
||||
|
||||
/* Check if the transfer has been cancelled by the user */
|
||||
if (shared_data->transfer_cancelled)
|
||||
{
|
||||
condvarWakeAll(&g_writeCondvar);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Read current data chunk */
|
||||
shared_data->read_error = !gamecardReadStorage(buf, blksize, offset);
|
||||
if (shared_data->read_error)
|
||||
{
|
||||
condvarWakeAll(&g_writeCondvar);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Remove certificate */
|
||||
if (!g_keepCertificate && offset == 0) memset(buf + GAMECARD_CERTIFICATE_OFFSET, 0xFF, sizeof(FsGameCardCertificate));
|
||||
|
||||
/* Wait until the previous data chunk has been written */
|
||||
mutexLock(&g_fileMutex);
|
||||
|
||||
if (shared_data->data_size && !shared_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex);
|
||||
|
||||
if (shared_data->write_error)
|
||||
{
|
||||
mutexUnlock(&g_fileMutex);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Copy current file data chunk to the shared buffer */
|
||||
memcpy(shared_data->data, buf, blksize);
|
||||
shared_data->data_size = blksize;
|
||||
|
||||
/* Wake up the write thread to continue writing data */
|
||||
mutexUnlock(&g_fileMutex);
|
||||
condvarWakeAll(&g_writeCondvar);
|
||||
}
|
||||
|
||||
free(buf);
|
||||
|
||||
return (shared_data->read_error ? -3 : 0);
|
||||
}
|
||||
|
||||
static int write_thread_func(void *arg)
|
||||
{
|
||||
ThreadSharedData *shared_data = (ThreadSharedData*)arg;
|
||||
if (!shared_data || !shared_data->data)
|
||||
{
|
||||
shared_data->write_error = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
while(shared_data->data_written < shared_data->total_size)
|
||||
{
|
||||
/* Wait until the current file data chunk has been read */
|
||||
mutexLock(&g_fileMutex);
|
||||
|
||||
if (!shared_data->data_size && !shared_data->read_error) condvarWait(&g_writeCondvar, &g_fileMutex);
|
||||
|
||||
if (shared_data->read_error || shared_data->transfer_cancelled)
|
||||
{
|
||||
mutexUnlock(&g_fileMutex);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Write current file data chunk */
|
||||
shared_data->write_error = !usbSendFileData(shared_data->data, shared_data->data_size);
|
||||
if (!shared_data->write_error)
|
||||
{
|
||||
shared_data->data_written += shared_data->data_size;
|
||||
shared_data->data_size = 0;
|
||||
}
|
||||
|
||||
/* Wake up the read thread to continue reading data */
|
||||
mutexUnlock(&g_fileMutex);
|
||||
condvarWakeAll(&g_readCondvar);
|
||||
|
||||
if (shared_data->write_error) break;
|
||||
}
|
||||
|
||||
return (shared_data->write_error ? -2 : 0);
|
||||
}
|
|
@ -32,6 +32,8 @@
|
|||
|
||||
#define GAMECARD_UPDATE_TID (u64)0x0100000000000816
|
||||
|
||||
#define GAMECARD_CERTIFICATE_OFFSET 0x7000
|
||||
|
||||
/// Encrypted using AES-128-ECB with the common titlekek generator key (stored in the .rodata segment from the Lotus firmware).
|
||||
typedef struct {
|
||||
u64 package_id; ///< Matches package_id from GameCardHeader.
|
||||
|
|
899
source/main.c
899
source/main.c
|
@ -19,204 +19,150 @@
|
|||
*/
|
||||
|
||||
#include "utils.h"
|
||||
#include "nca.h"
|
||||
#include "title.h"
|
||||
#include "pfs.h"
|
||||
#include "romfs.h"
|
||||
#include "gamecard.h"
|
||||
#include "usb.h"
|
||||
|
||||
#define BLOCK_SIZE 0x800000
|
||||
#define OUTPATH "sdmc:/systitle_dumps"
|
||||
#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE
|
||||
|
||||
static u8 *buf = NULL;
|
||||
static FILE *filefd = NULL;
|
||||
static char path[FS_MAX_PATH * 2] = {0};
|
||||
/* Type definitions. */
|
||||
|
||||
static void consolePrint(const char *text, ...)
|
||||
typedef void (*MenuElementOptionFunction)(u32 idx);
|
||||
|
||||
typedef struct {
|
||||
u32 selected; ///< Used to keep track of the selected option.
|
||||
MenuElementOptionFunction options_func; ///< Pointer to a function to be called each time a new option is selected. Should be set to NULL if not used.
|
||||
const char **options; ///< Pointer to multiple char pointers with strings representing options. Last element must be set to NULL.
|
||||
} MenuElementOption;
|
||||
|
||||
typedef bool (*MenuElementFunction)(void);
|
||||
|
||||
typedef struct {
|
||||
const char *str; ///< Pointer to a string to be printed for this menu element.
|
||||
void *child_menu; ///< Pointer to a child Menu element. Must be set to NULL if task_func != NULL.
|
||||
MenuElementFunction task_func; ///< Pointer to a function to be called by this element. Must be set to NULL if child_menu != NULL.
|
||||
MenuElementOption *element_options; ///< Options for this menu element. Should be set to NULL if not used.
|
||||
} MenuElement;
|
||||
|
||||
typedef struct _Menu {
|
||||
struct _Menu *parent; ///< Set to NULL in the root menu element.
|
||||
u32 selected, scroll; ///< Used to keep track of the selected element and scroll values.
|
||||
MenuElement **elements; ///< Element info from this menu. Last element must be set to NULL.
|
||||
} Menu;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
va_list v;
|
||||
va_start(v, text);
|
||||
vfprintf(stdout, text, v);
|
||||
va_end(v);
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
void *data;
|
||||
size_t data_size;
|
||||
size_t data_written;
|
||||
size_t total_size;
|
||||
bool read_error;
|
||||
bool write_error;
|
||||
bool transfer_cancelled;
|
||||
} ThreadSharedData;
|
||||
|
||||
static void dumpPartitionFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
|
||||
{
|
||||
if (!buf || !info || !nca_fs_ctx) return;
|
||||
/* Function prototypes. */
|
||||
|
||||
u32 pfs_entry_count = 0;
|
||||
PartitionFileSystemContext pfs_ctx = {0};
|
||||
PartitionFileSystemEntry *pfs_entry = NULL;
|
||||
char *pfs_entry_name = NULL;
|
||||
static void consolePrint(const char *text, ...);
|
||||
|
||||
size_t path_len = 0;
|
||||
*path = '\0';
|
||||
static u32 menuGetElementCount(const Menu *menu);
|
||||
|
||||
if (!pfsInitializeContext(&pfs_ctx, nca_fs_ctx))
|
||||
{
|
||||
consolePrint("pfs initialize ctx failed!\n");
|
||||
goto end;
|
||||
static bool sendGameCardKeyAreaViaUsb(void);
|
||||
static bool sendGameCardCertificateViaUsb(void);
|
||||
static bool sendGameCardImageViaUsb(void);
|
||||
|
||||
static void changeKeyAreaOption(u32 idx);
|
||||
static void changeCertificateOption(u32 idx);
|
||||
static void changeTrimOption(u32 idx);
|
||||
|
||||
static int read_thread_func(void *arg);
|
||||
static int write_thread_func(void *arg);
|
||||
|
||||
/* Global variables. */
|
||||
|
||||
static bool g_appendKeyArea = false, g_keepCertificate = false, g_trimDump = false;
|
||||
|
||||
static const char *g_xciOptions[] = { "no", "yes", NULL };
|
||||
|
||||
static MenuElement *g_xciMenuElements[] = {
|
||||
&(MenuElement){
|
||||
.str = "start dump",
|
||||
.child_menu = NULL,
|
||||
.task_func = &sendGameCardImageViaUsb,
|
||||
.element_options = NULL
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "append key area",
|
||||
.child_menu = NULL,
|
||||
.task_func = NULL,
|
||||
.element_options = &(MenuElementOption){
|
||||
.selected = 0,
|
||||
.options_func = &changeKeyAreaOption,
|
||||
.options = g_xciOptions
|
||||
}
|
||||
|
||||
if (!(pfs_entry_count = pfsGetEntryCount(&pfs_ctx)))
|
||||
{
|
||||
consolePrint("pfs entry count is zero!\n");
|
||||
goto end;
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "keep certificate",
|
||||
.child_menu = NULL,
|
||||
.task_func = NULL,
|
||||
.element_options = &(MenuElementOption){
|
||||
.selected = 0,
|
||||
.options_func = &changeCertificateOption,
|
||||
.options = g_xciOptions
|
||||
}
|
||||
|
||||
snprintf(path, sizeof(path), OUTPATH "/%016lX - %s/%s (%s)/Section %u (%s)", info->meta_key.id, info->app_metadata->lang_entry.name, ((NcaContext*)nca_fs_ctx->nca_ctx)->content_id_str, \
|
||||
titleGetNcmContentTypeName(((NcaContext*)nca_fs_ctx->nca_ctx)->content_type), nca_fs_ctx->section_num, ncaGetFsSectionTypeName(nca_fs_ctx->section_type));
|
||||
utilsCreateDirectoryTree(path, true);
|
||||
path_len = strlen(path);
|
||||
|
||||
for(u32 i = 0; i < pfs_entry_count; i++)
|
||||
{
|
||||
if (!(pfs_entry = pfsGetEntryByIndex(&pfs_ctx, i)) || !(pfs_entry_name = pfsGetEntryNameByIndex(&pfs_ctx, i)) || !strlen(pfs_entry_name))
|
||||
{
|
||||
consolePrint("pfs get entry / get name #%u failed!\n", i);
|
||||
goto end;
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "trim dump",
|
||||
.child_menu = NULL,
|
||||
.task_func = NULL,
|
||||
.element_options = &(MenuElementOption){
|
||||
.selected = 0,
|
||||
.options_func = &changeTrimOption,
|
||||
.options = g_xciOptions
|
||||
}
|
||||
},
|
||||
NULL
|
||||
};
|
||||
|
||||
path[path_len] = '\0';
|
||||
strcat(path, "/");
|
||||
strcat(path, pfs_entry_name);
|
||||
utilsReplaceIllegalCharacters(path + path_len + 1, true);
|
||||
static Menu g_xciMenu = {
|
||||
.parent = NULL,
|
||||
.selected = 0,
|
||||
.scroll = 0,
|
||||
.elements = g_xciMenuElements
|
||||
};
|
||||
|
||||
filefd = fopen(path, "wb");
|
||||
if (!filefd)
|
||||
{
|
||||
consolePrint("failed to create \"%s\"!\n", path);
|
||||
goto end;
|
||||
}
|
||||
static MenuElement *g_rootMenuElements[] = {
|
||||
&(MenuElement){
|
||||
.str = "dump key area",
|
||||
.child_menu = NULL,
|
||||
.task_func = &sendGameCardKeyAreaViaUsb,
|
||||
.element_options = NULL
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "dump certificate",
|
||||
.child_menu = NULL,
|
||||
.task_func = &sendGameCardCertificateViaUsb,
|
||||
.element_options = NULL
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "dump xci",
|
||||
.child_menu = &g_xciMenu,
|
||||
.task_func = NULL,
|
||||
.element_options = NULL
|
||||
},
|
||||
NULL
|
||||
};
|
||||
|
||||
consolePrint("dumping \"%s\"...\n", pfs_entry_name);
|
||||
static Menu g_rootMenu = {
|
||||
.parent = NULL,
|
||||
.selected = 0,
|
||||
.scroll = 0,
|
||||
.elements = g_rootMenuElements
|
||||
};
|
||||
|
||||
u64 blksize = BLOCK_SIZE;
|
||||
for(u64 j = 0; j < pfs_entry->size; j += blksize)
|
||||
{
|
||||
if (blksize > (pfs_entry->size - j)) blksize = (pfs_entry->size - j);
|
||||
static Mutex g_fileMutex = 0;
|
||||
static CondVar g_readCondvar = 0, g_writeCondvar = 0;
|
||||
|
||||
if (!pfsReadEntryData(&pfs_ctx, pfs_entry, buf, blksize, j))
|
||||
{
|
||||
consolePrint("failed to read 0x%lX block from offset 0x%lX!\n", blksize, j);
|
||||
goto end;
|
||||
}
|
||||
|
||||
fwrite(buf, 1, blksize, filefd);
|
||||
}
|
||||
|
||||
fclose(filefd);
|
||||
filefd = NULL;
|
||||
}
|
||||
|
||||
consolePrint("pfs dump complete\n");
|
||||
|
||||
end:
|
||||
if (filefd)
|
||||
{
|
||||
fclose(filefd);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
if (*path) utilsCommitFileSystemChangesByPath(path);
|
||||
|
||||
pfsFreeContext(&pfs_ctx);
|
||||
}
|
||||
|
||||
static void dumpRomFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
|
||||
{
|
||||
if (!buf || !info || !nca_fs_ctx) return;
|
||||
|
||||
u64 romfs_file_table_offset = 0;
|
||||
RomFileSystemContext romfs_ctx = {0};
|
||||
RomFileSystemFileEntry *romfs_file_entry = NULL;
|
||||
|
||||
size_t path_len = 0;
|
||||
*path = '\0';
|
||||
|
||||
if (!romfsInitializeContext(&romfs_ctx, nca_fs_ctx))
|
||||
{
|
||||
consolePrint("romfs initialize ctx failed!\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
snprintf(path, sizeof(path), OUTPATH "/%016lX - %s/%s (%s)/Section %u (%s)", info->meta_key.id, info->app_metadata->lang_entry.name, ((NcaContext*)nca_fs_ctx->nca_ctx)->content_id_str, \
|
||||
titleGetNcmContentTypeName(((NcaContext*)nca_fs_ctx->nca_ctx)->content_type), nca_fs_ctx->section_num, ncaGetFsSectionTypeName(nca_fs_ctx->section_type));
|
||||
utilsCreateDirectoryTree(path, true);
|
||||
path_len = strlen(path);
|
||||
|
||||
while(romfs_file_table_offset < romfs_ctx.file_table_size)
|
||||
{
|
||||
if (!(romfs_file_entry = romfsGetFileEntryByOffset(&romfs_ctx, romfs_file_table_offset)) || \
|
||||
!romfsGeneratePathFromFileEntry(&romfs_ctx, romfs_file_entry, path + path_len, sizeof(path) - path_len, RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly))
|
||||
{
|
||||
consolePrint("romfs get entry / generate path failed for 0x%lX!\n", romfs_file_table_offset);
|
||||
goto end;
|
||||
}
|
||||
|
||||
utilsCreateDirectoryTree(path, false);
|
||||
|
||||
filefd = fopen(path, "wb");
|
||||
if (!filefd)
|
||||
{
|
||||
consolePrint("failed to create \"%s\"!\n", path);
|
||||
goto end;
|
||||
}
|
||||
|
||||
consolePrint("dumping \"%s\"...\n", path + path_len);
|
||||
|
||||
u64 blksize = BLOCK_SIZE;
|
||||
for(u64 j = 0; j < romfs_file_entry->size; j += blksize)
|
||||
{
|
||||
if (blksize > (romfs_file_entry->size - j)) blksize = (romfs_file_entry->size - j);
|
||||
|
||||
if (!romfsReadFileEntryData(&romfs_ctx, romfs_file_entry, buf, blksize, j))
|
||||
{
|
||||
consolePrint("failed to read 0x%lX block from offset 0x%lX!\n", blksize, j);
|
||||
goto end;
|
||||
}
|
||||
|
||||
fwrite(buf, 1, blksize, filefd);
|
||||
}
|
||||
|
||||
fclose(filefd);
|
||||
filefd = NULL;
|
||||
|
||||
romfs_file_table_offset += ALIGN_UP(sizeof(RomFileSystemFileEntry) + romfs_file_entry->name_length, 4);
|
||||
}
|
||||
|
||||
consolePrint("romfs dump complete\n");
|
||||
|
||||
end:
|
||||
if (filefd)
|
||||
{
|
||||
fclose(filefd);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
if (*path) utilsCommitFileSystemChangesByPath(path);
|
||||
|
||||
romfsFreeContext(&romfs_ctx);
|
||||
}
|
||||
|
||||
static void dumpFsSection(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
|
||||
{
|
||||
if (!buf || !info || !nca_fs_ctx) return;
|
||||
|
||||
switch(nca_fs_ctx->section_type)
|
||||
{
|
||||
case NcaFsSectionType_PartitionFs:
|
||||
dumpPartitionFs(info, nca_fs_ctx);
|
||||
break;
|
||||
case NcaFsSectionType_RomFs:
|
||||
case NcaFsSectionType_Nca0RomFs:
|
||||
dumpRomFs(info, nca_fs_ctx);
|
||||
break;
|
||||
default:
|
||||
consolePrint("invalid section type!\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
static char path[FS_MAX_PATH] = {0};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
@ -225,6 +171,9 @@ int main(int argc, char *argv[])
|
|||
|
||||
int ret = 0;
|
||||
|
||||
Menu *cur_menu = &g_rootMenu;
|
||||
u32 element_count = menuGetElementCount(cur_menu), page_size = 30;
|
||||
|
||||
LOGFILE(APP_TITLE " starting.");
|
||||
|
||||
consoleInit(NULL);
|
||||
|
@ -237,81 +186,36 @@ int main(int argc, char *argv[])
|
|||
goto out;
|
||||
}
|
||||
|
||||
u32 app_count = 0;
|
||||
TitleApplicationMetadata **app_metadata = NULL;
|
||||
TitleInfo *cur_title_info = NULL;
|
||||
|
||||
u32 selected_idx = 0, menu = 0, page_size = 30, scroll = 0;
|
||||
u32 title_idx = 0, title_scroll = 0, nca_idx = 0;
|
||||
char nca_id_str[0x21] = {0};
|
||||
|
||||
NcaContext *nca_ctx = NULL;
|
||||
Ticket tik = {0};
|
||||
|
||||
app_metadata = titleGetApplicationMetadataEntries(true, &app_count);
|
||||
if (!app_metadata || !app_count)
|
||||
{
|
||||
consolePrint("app metadata failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
consolePrint("app metadata succeeded\n");
|
||||
|
||||
buf = malloc(BLOCK_SIZE);
|
||||
if (!buf)
|
||||
{
|
||||
consolePrint("buf failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
consolePrint("buf succeeded\n");
|
||||
|
||||
nca_ctx = calloc(1, sizeof(NcaContext));
|
||||
if (!nca_ctx)
|
||||
{
|
||||
consolePrint("nca ctx buf failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
consolePrint("nca ctx buf succeeded\n");
|
||||
|
||||
utilsSleep(1);
|
||||
|
||||
while(true)
|
||||
while(appletMainLoop())
|
||||
{
|
||||
consoleClear();
|
||||
printf("\npress b to %s.\n\n", cur_menu->parent ? "go back" : "exit");
|
||||
|
||||
printf("select a %s.", menu == 0 ? "system title to view its contents" : (menu == 1 ? "content" : "fs section"));
|
||||
printf("\npress b to %s.\n\n", menu == 0 ? "exit" : "go back");
|
||||
u32 limit = (cur_menu->scroll + page_size);
|
||||
MenuElement *selected_element = cur_menu->elements[cur_menu->selected];
|
||||
MenuElementOption *selected_element_options = selected_element->element_options;
|
||||
|
||||
if (menu == 0) printf("title: %u / %u\n\n", selected_idx + 1, app_count);
|
||||
if (menu >= 1) printf("selected title: %016lX - %s\n\n", app_metadata[title_idx]->title_id, app_metadata[title_idx]->lang_entry.name);
|
||||
if (menu == 2) printf("selected content: %s (%s)\n\n", nca_id_str, titleGetNcmContentTypeName(cur_title_info->content_infos[nca_idx].content_type));
|
||||
|
||||
u32 max_val = (menu == 0 ? app_count : (menu == 1 ? cur_title_info->content_count : NCA_FS_HEADER_COUNT));
|
||||
for(u32 i = scroll; i < max_val; i++)
|
||||
for(u32 i = cur_menu->scroll; cur_menu->elements[i] && i < element_count; i++)
|
||||
{
|
||||
if (i >= (scroll + page_size)) break;
|
||||
if (i >= limit) break;
|
||||
|
||||
printf("%s", i == selected_idx ? " -> " : " ");
|
||||
MenuElement *cur_element = cur_menu->elements[i];
|
||||
MenuElementOption *cur_options = cur_menu->elements[i]->element_options;
|
||||
|
||||
if (menu == 0)
|
||||
printf("%s%s", i == cur_menu->selected ? " -> " : " ", cur_element->str);
|
||||
|
||||
if (cur_options)
|
||||
{
|
||||
printf("%016lX - %s\n", app_metadata[i]->title_id, app_metadata[i]->lang_entry.name);
|
||||
} else
|
||||
if (menu == 1)
|
||||
{
|
||||
utilsGenerateHexStringFromData(nca_id_str, sizeof(nca_id_str), cur_title_info->content_infos[i].content_id.c, SHA256_HASH_SIZE / 2);
|
||||
printf("%s (%s)\n", nca_id_str, titleGetNcmContentTypeName(cur_title_info->content_infos[i].content_type));
|
||||
} else
|
||||
if (menu == 2)
|
||||
{
|
||||
printf("fs section #%u (%s)\n", i + 1, ncaGetFsSectionTypeName(nca_ctx->fs_contexts[i].section_type));
|
||||
}
|
||||
printf(": ");
|
||||
if (cur_options->selected > 0) printf("< ");
|
||||
printf("%s", cur_options->options[cur_options->selected]);
|
||||
if (cur_options->options[cur_options->selected + 1]) printf(" >");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
u64 btn_down = 0, btn_held = 0;
|
||||
|
@ -324,120 +228,80 @@ int main(int argc, char *argv[])
|
|||
|
||||
if (btn_down & KEY_A)
|
||||
{
|
||||
bool error = false;
|
||||
Menu *child_menu = (Menu*)selected_element->child_menu;
|
||||
|
||||
if (menu == 0)
|
||||
if (child_menu)
|
||||
{
|
||||
title_idx = selected_idx;
|
||||
title_scroll = scroll;
|
||||
child_menu->parent = cur_menu;
|
||||
cur_menu = child_menu;
|
||||
element_count = menuGetElementCount(cur_menu);
|
||||
} else
|
||||
if (menu == 1)
|
||||
if (selected_element->task_func)
|
||||
{
|
||||
nca_idx = selected_idx;
|
||||
utilsGenerateHexStringFromData(nca_id_str, sizeof(nca_id_str), cur_title_info->content_infos[nca_idx].content_id.c, SHA256_HASH_SIZE / 2);
|
||||
}
|
||||
|
||||
menu++;
|
||||
|
||||
if (menu == 1)
|
||||
{
|
||||
cur_title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, app_metadata[title_idx]->title_id);
|
||||
if (!cur_title_info)
|
||||
{
|
||||
consolePrint("failed to get title info\n");
|
||||
error = true;
|
||||
}
|
||||
} else
|
||||
if (menu == 2)
|
||||
{
|
||||
if (!ncaInitializeContext(nca_ctx, cur_title_info->storage_id, 0, &(cur_title_info->content_infos[nca_idx]), &tik))
|
||||
{
|
||||
consolePrint("nca initialize ctx failed\n");
|
||||
error = true;
|
||||
}
|
||||
} else
|
||||
if (menu == 3)
|
||||
{
|
||||
consoleClear();
|
||||
utilsChangeHomeButtonBlockStatus(true);
|
||||
dumpFsSection(cur_title_info, &(nca_ctx->fs_contexts[selected_idx]));
|
||||
utilsChangeHomeButtonBlockStatus(false);
|
||||
}
|
||||
|
||||
if (error || menu >= 3)
|
||||
{
|
||||
utilsSleep(3);
|
||||
menu--;
|
||||
} else {
|
||||
selected_idx = scroll = 0;
|
||||
selected_element->task_func();
|
||||
}
|
||||
} else
|
||||
if ((btn_down & KEY_DDOWN) || (btn_held & (KEY_LSTICK_DOWN | KEY_RSTICK_DOWN)))
|
||||
{
|
||||
selected_idx++;
|
||||
cur_menu->selected++;
|
||||
|
||||
if (selected_idx >= max_val)
|
||||
if (!cur_menu->elements[cur_menu->selected])
|
||||
{
|
||||
if (btn_down & KEY_DDOWN)
|
||||
{
|
||||
selected_idx = scroll = 0;
|
||||
cur_menu->selected = 0;
|
||||
cur_menu->scroll = 0;
|
||||
} else {
|
||||
selected_idx = (max_val - 1);
|
||||
cur_menu->selected--;
|
||||
}
|
||||
} else
|
||||
if (selected_idx >= (scroll + (page_size / 2)) && max_val > (scroll + page_size))
|
||||
if (cur_menu->selected >= limit && cur_menu->elements[cur_menu->selected + 1])
|
||||
{
|
||||
scroll++;
|
||||
cur_menu->scroll++;
|
||||
}
|
||||
} else
|
||||
if ((btn_down & KEY_DUP) || (btn_held & (KEY_LSTICK_UP | KEY_RSTICK_UP)))
|
||||
{
|
||||
selected_idx--;
|
||||
cur_menu->selected--;
|
||||
|
||||
if (selected_idx == UINT32_MAX)
|
||||
if (cur_menu->selected == UINT32_MAX)
|
||||
{
|
||||
if (btn_down & KEY_DUP)
|
||||
{
|
||||
selected_idx = (max_val - 1);
|
||||
scroll = (max_val >= page_size ? (max_val - page_size) : 0);
|
||||
cur_menu->selected = (element_count - 1);
|
||||
cur_menu->scroll = (element_count > page_size ? (element_count - page_size) : 0);
|
||||
} else {
|
||||
selected_idx = 0;
|
||||
cur_menu->selected = 0;
|
||||
}
|
||||
} else
|
||||
if (selected_idx < (scroll + (page_size / 2)) && scroll > 0)
|
||||
if (cur_menu->selected < cur_menu->scroll && cur_menu->scroll > 0)
|
||||
{
|
||||
scroll--;
|
||||
cur_menu->scroll--;
|
||||
}
|
||||
} else
|
||||
if ((btn_down & (KEY_DRIGHT | KEY_LSTICK_RIGHT | KEY_RSTICK_RIGHT)) && selected_element_options)
|
||||
{
|
||||
selected_element_options->selected++;
|
||||
if (!selected_element_options->options[selected_element_options->selected]) selected_element_options->selected--;
|
||||
if (selected_element_options->options_func) selected_element_options->options_func(selected_element_options->selected);
|
||||
} else
|
||||
if ((btn_down & (KEY_DLEFT | KEY_LSTICK_LEFT | KEY_RSTICK_LEFT)) && selected_element_options)
|
||||
{
|
||||
selected_element_options->selected--;
|
||||
if (selected_element_options->selected == UINT32_MAX) selected_element_options->selected = 0;
|
||||
if (selected_element_options->options_func) selected_element_options->options_func(selected_element_options->selected);
|
||||
} else
|
||||
if (btn_down & KEY_B)
|
||||
{
|
||||
menu--;
|
||||
if (!cur_menu->parent) break;
|
||||
|
||||
if (menu == UINT32_MAX)
|
||||
{
|
||||
break;
|
||||
} else {
|
||||
selected_idx = (menu == 0 ? title_idx : nca_idx);
|
||||
scroll = (menu == 0 ? title_scroll : 0);
|
||||
}
|
||||
cur_menu = cur_menu->parent;
|
||||
element_count = menuGetElementCount(cur_menu);
|
||||
}
|
||||
|
||||
if (btn_held & (KEY_LSTICK_DOWN | KEY_RSTICK_DOWN | KEY_LSTICK_UP | KEY_RSTICK_UP)) svcSleepThread(50000000); // 50 ms
|
||||
}
|
||||
|
||||
out2:
|
||||
if (menu != UINT32_MAX)
|
||||
{
|
||||
consolePrint("press any button to exit\n");
|
||||
utilsWaitForButtonPress(KEY_NONE);
|
||||
}
|
||||
|
||||
if (nca_ctx) free(nca_ctx);
|
||||
|
||||
if (buf) free(buf);
|
||||
|
||||
if (app_metadata) free(app_metadata);
|
||||
|
||||
out:
|
||||
utilsCloseResources();
|
||||
|
||||
|
@ -445,3 +309,396 @@ out:
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void consolePrint(const char *text, ...)
|
||||
{
|
||||
va_list v;
|
||||
va_start(v, text);
|
||||
vfprintf(stdout, text, v);
|
||||
va_end(v);
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
static u32 menuGetElementCount(const Menu *menu)
|
||||
{
|
||||
if (!menu || !menu->elements || !menu->elements[0]) return 0;
|
||||
|
||||
u32 cnt;
|
||||
for(cnt = 0; menu->elements[cnt]; cnt++);
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static void waitForGameCardAndUsb(void)
|
||||
{
|
||||
consoleClear();
|
||||
consolePrint("waiting for gamecard and usb session...\n");
|
||||
|
||||
while(true)
|
||||
{
|
||||
if (gamecardGetStatus() == GameCardStatus_InsertedAndInfoLoaded && usbIsReady()) break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool sendFileData(const char *path, void *data, size_t data_size)
|
||||
{
|
||||
if (!path || !strlen(path) || !data || !data_size)
|
||||
{
|
||||
consolePrint("invalid parameters to send file data!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!usbSendFileProperties(data_size, path))
|
||||
{
|
||||
consolePrint("failed to send file properties for \"%s\"!\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!usbSendFileData(data, data_size))
|
||||
{
|
||||
consolePrint("failed to send file data for \"%s\"!\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool dumpGameCardKeyArea(GameCardKeyArea *out)
|
||||
{
|
||||
if (!out)
|
||||
{
|
||||
consolePrint("invalid parameters to dump key area!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gamecardGetKeyArea(out))
|
||||
{
|
||||
consolePrint("failed to get gamecard key area\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
consolePrint("get gamecard key area ok\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sendGameCardKeyAreaViaUsb(void)
|
||||
{
|
||||
waitForGameCardAndUsb();
|
||||
|
||||
utilsChangeHomeButtonBlockStatus(false);
|
||||
|
||||
GameCardKeyArea gc_key_area = {0};
|
||||
bool success = false;
|
||||
|
||||
if (!dumpGameCardKeyArea(&gc_key_area)) goto end;
|
||||
|
||||
sprintf(path, "card_key_area_%016lX.bin", gc_key_area.initial_data.key_source.package_id);
|
||||
if (!sendFileData(path, &gc_key_area, sizeof(GameCardKeyArea))) goto end;
|
||||
|
||||
consolePrint("successfully sent key area as \"%s\"\n", path);
|
||||
success = true;
|
||||
|
||||
end:
|
||||
utilsChangeHomeButtonBlockStatus(false);
|
||||
|
||||
consolePrint("press any button to continue");
|
||||
utilsWaitForButtonPress(KEY_NONE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool sendGameCardCertificateViaUsb(void)
|
||||
{
|
||||
waitForGameCardAndUsb();
|
||||
|
||||
utilsChangeHomeButtonBlockStatus(true);
|
||||
|
||||
FsGameCardCertificate gc_cert = {0};
|
||||
char device_id_str[0x21] = {0};
|
||||
bool success = false;
|
||||
|
||||
if (!gamecardGetCertificate(&gc_cert))
|
||||
{
|
||||
consolePrint("failed to get gamecard certificate\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
consolePrint("get gamecard certificate ok\n");
|
||||
|
||||
utilsGenerateHexStringFromData(device_id_str, 0x21, gc_cert.device_id, 0x10);
|
||||
sprintf(path, "card_certificate_%s.bin", device_id_str);
|
||||
if (!sendFileData(path, &gc_cert, sizeof(FsGameCardCertificate))) goto end;
|
||||
|
||||
consolePrint("successfully sent certificate as \"%s\"\n", path);
|
||||
success = true;
|
||||
|
||||
end:
|
||||
utilsChangeHomeButtonBlockStatus(false);
|
||||
|
||||
consolePrint("press any button to continue");
|
||||
utilsWaitForButtonPress(KEY_NONE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool sendGameCardImageViaUsb(void)
|
||||
{
|
||||
waitForGameCardAndUsb();
|
||||
|
||||
utilsChangeHomeButtonBlockStatus(true);
|
||||
|
||||
u64 gc_size = 0;
|
||||
GameCardKeyArea gc_key_area = {0};
|
||||
|
||||
ThreadSharedData shared_data = {0};
|
||||
thrd_t read_thread, write_thread;
|
||||
|
||||
bool success = false;
|
||||
|
||||
consolePrint("gamecard image dump\nappend key area: %s | keep certificate: %s | trim dump: %s\n\n", g_appendKeyArea ? "yes" : "no", g_keepCertificate ? "yes" : "no", g_trimDump ? "yes" : "no");
|
||||
|
||||
shared_data.data = usbAllocatePageAlignedBuffer(BLOCK_SIZE);
|
||||
if (!shared_data.data)
|
||||
{
|
||||
consolePrint("failed to allocate memory for the dump procedure!\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
if ((!g_trimDump && !gamecardGetTotalSize(&gc_size)) || (g_trimDump && !gamecardGetTrimmedSize(&gc_size)) || !gc_size)
|
||||
{
|
||||
consolePrint("failed to get gamecard size!\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
shared_data.total_size = gc_size;
|
||||
|
||||
consolePrint("gamecard size: 0x%lX\n", gc_size);
|
||||
|
||||
if (g_appendKeyArea)
|
||||
{
|
||||
gc_size += sizeof(GameCardKeyArea);
|
||||
if (!dumpGameCardKeyArea(&gc_key_area)) goto end;
|
||||
consolePrint("gamecard size (with key area): 0x%lX\n", gc_size);
|
||||
}
|
||||
|
||||
sprintf(path, "gamecard (%s) (%s) (%s).xci", g_appendKeyArea ? "keyarea" : "keyarealess", g_keepCertificate ? "cert" : "certless", g_trimDump ? "trimmed" : "untrimmed");
|
||||
if (!usbSendFileProperties(gc_size, path))
|
||||
{
|
||||
consolePrint("failed to send file properties for \"%s\"!\n", path);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (g_appendKeyArea && !usbSendFileData(&gc_key_area, sizeof(GameCardKeyArea)))
|
||||
{
|
||||
consolePrint("failed to send gamecard key area data!\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
consolePrint("creating threads\n");
|
||||
thrd_create(&read_thread, read_thread_func, &shared_data);
|
||||
thrd_create(&write_thread, write_thread_func, &shared_data);
|
||||
|
||||
u8 prev_time = 0;
|
||||
u64 prev_size = 0;
|
||||
u8 percent = 0;
|
||||
|
||||
time_t start = 0, btn_cancel_start_tmr = 0, btn_cancel_end_tmr = 0;
|
||||
bool btn_cancel_cur_state = false, btn_cancel_prev_state = false;
|
||||
|
||||
consolePrint("hold b to cancel\n\n");
|
||||
|
||||
start = time(NULL);
|
||||
|
||||
while(shared_data.data_written < shared_data.total_size)
|
||||
{
|
||||
if (shared_data.read_error || shared_data.write_error) break;
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct tm *ts = localtime(&now);
|
||||
size_t size = shared_data.data_written;
|
||||
|
||||
hidScanInput();
|
||||
btn_cancel_cur_state = (utilsHidKeysAllHeld() & KEY_B);
|
||||
|
||||
if (btn_cancel_cur_state && btn_cancel_cur_state != btn_cancel_prev_state)
|
||||
{
|
||||
btn_cancel_start_tmr = now;
|
||||
} else
|
||||
if (btn_cancel_cur_state && btn_cancel_cur_state == btn_cancel_prev_state)
|
||||
{
|
||||
btn_cancel_end_tmr = now;
|
||||
if ((btn_cancel_end_tmr - btn_cancel_start_tmr) >= 3)
|
||||
{
|
||||
mutexLock(&g_fileMutex);
|
||||
usbCancelFileTransfer();
|
||||
shared_data.transfer_cancelled = true;
|
||||
mutexUnlock(&g_fileMutex);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
btn_cancel_start_tmr = btn_cancel_end_tmr = 0;
|
||||
}
|
||||
|
||||
btn_cancel_prev_state = btn_cancel_cur_state;
|
||||
|
||||
if (prev_time == ts->tm_sec || prev_size == size) continue;
|
||||
|
||||
percent = (u8)((size * 100) / shared_data.total_size);
|
||||
|
||||
prev_time = ts->tm_sec;
|
||||
prev_size = size;
|
||||
|
||||
printf("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_data.total_size, percent, (now - start));
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
start = (time(NULL) - start);
|
||||
|
||||
consolePrint("\nwaiting for threads to join\n");
|
||||
thrd_join(read_thread, NULL);
|
||||
consolePrint("read_thread done: %lu\n", time(NULL));
|
||||
thrd_join(write_thread, NULL);
|
||||
consolePrint("write_thread done: %lu\n", time(NULL));
|
||||
|
||||
if (shared_data.read_error || shared_data.write_error)
|
||||
{
|
||||
consolePrint("usb transfer error\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (shared_data.transfer_cancelled)
|
||||
{
|
||||
consolePrint("process cancelled\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
consolePrint("process completed in %lu seconds\n", start);
|
||||
success = true;
|
||||
|
||||
end:
|
||||
if (shared_data.data) free(shared_data.data);
|
||||
|
||||
utilsChangeHomeButtonBlockStatus(false);
|
||||
|
||||
consolePrint("press any button to continue");
|
||||
utilsWaitForButtonPress(KEY_NONE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static void changeKeyAreaOption(u32 idx)
|
||||
{
|
||||
g_appendKeyArea = (idx > 0);
|
||||
}
|
||||
|
||||
static void changeCertificateOption(u32 idx)
|
||||
{
|
||||
g_keepCertificate = (idx > 0);
|
||||
}
|
||||
|
||||
static void changeTrimOption(u32 idx)
|
||||
{
|
||||
g_trimDump = (idx > 0);
|
||||
}
|
||||
|
||||
static int read_thread_func(void *arg)
|
||||
{
|
||||
ThreadSharedData *shared_data = (ThreadSharedData*)arg;
|
||||
if (!shared_data || !shared_data->data || !shared_data->total_size)
|
||||
{
|
||||
shared_data->read_error = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
u8 *buf = malloc(BLOCK_SIZE);
|
||||
if (!buf)
|
||||
{
|
||||
shared_data->read_error = true;
|
||||
return -2;
|
||||
}
|
||||
|
||||
for(u64 offset = 0, blksize = BLOCK_SIZE; offset < shared_data->total_size; offset += blksize)
|
||||
{
|
||||
if (blksize > (shared_data->total_size - offset)) blksize = (shared_data->total_size - offset);
|
||||
|
||||
/* Check if the transfer has been cancelled by the user */
|
||||
if (shared_data->transfer_cancelled)
|
||||
{
|
||||
condvarWakeAll(&g_writeCondvar);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Read current data chunk */
|
||||
shared_data->read_error = !gamecardReadStorage(buf, blksize, offset);
|
||||
if (shared_data->read_error)
|
||||
{
|
||||
condvarWakeAll(&g_writeCondvar);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Remove certificate */
|
||||
if (!g_keepCertificate && offset == 0) memset(buf + GAMECARD_CERTIFICATE_OFFSET, 0xFF, sizeof(FsGameCardCertificate));
|
||||
|
||||
/* Wait until the previous data chunk has been written */
|
||||
mutexLock(&g_fileMutex);
|
||||
|
||||
if (shared_data->data_size && !shared_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex);
|
||||
|
||||
if (shared_data->write_error)
|
||||
{
|
||||
mutexUnlock(&g_fileMutex);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Copy current file data chunk to the shared buffer */
|
||||
memcpy(shared_data->data, buf, blksize);
|
||||
shared_data->data_size = blksize;
|
||||
|
||||
/* Wake up the write thread to continue writing data */
|
||||
mutexUnlock(&g_fileMutex);
|
||||
condvarWakeAll(&g_writeCondvar);
|
||||
}
|
||||
|
||||
free(buf);
|
||||
|
||||
return (shared_data->read_error ? -3 : 0);
|
||||
}
|
||||
|
||||
static int write_thread_func(void *arg)
|
||||
{
|
||||
ThreadSharedData *shared_data = (ThreadSharedData*)arg;
|
||||
if (!shared_data || !shared_data->data)
|
||||
{
|
||||
shared_data->write_error = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
while(shared_data->data_written < shared_data->total_size)
|
||||
{
|
||||
/* Wait until the current file data chunk has been read */
|
||||
mutexLock(&g_fileMutex);
|
||||
|
||||
if (!shared_data->data_size && !shared_data->read_error) condvarWait(&g_writeCondvar, &g_fileMutex);
|
||||
|
||||
if (shared_data->read_error || shared_data->transfer_cancelled)
|
||||
{
|
||||
mutexUnlock(&g_fileMutex);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Write current file data chunk */
|
||||
shared_data->write_error = !usbSendFileData(shared_data->data, shared_data->data_size);
|
||||
if (!shared_data->write_error)
|
||||
{
|
||||
shared_data->data_written += shared_data->data_size;
|
||||
shared_data->data_size = 0;
|
||||
}
|
||||
|
||||
/* Wake up the read thread to continue reading data */
|
||||
mutexUnlock(&g_fileMutex);
|
||||
condvarWakeAll(&g_readCondvar);
|
||||
|
||||
if (shared_data->write_error) break;
|
||||
}
|
||||
|
||||
return (shared_data->write_error ? -2 : 0);
|
||||
}
|
||||
|
|
122
source/usb.c
122
source/usb.c
|
@ -29,7 +29,7 @@
|
|||
#define USB_CMD_HEADER_MAGIC 0x4E584454 /* "NXDT". */
|
||||
|
||||
#define USB_TRANSFER_ALIGNMENT 0x1000 /* 4 KiB. */
|
||||
#define USB_TRANSFER_TIMEOUT 6 /* 6 seconds. */
|
||||
#define USB_TRANSFER_TIMEOUT 5 /* 5 seconds. */
|
||||
|
||||
/* Type definitions. */
|
||||
|
||||
|
@ -89,7 +89,7 @@ typedef enum {
|
|||
|
||||
typedef struct {
|
||||
u32 magic;
|
||||
u32 status;
|
||||
u32 status; ///< UsbStatusType.
|
||||
u8 reserved[0x8];
|
||||
} UsbStatus;
|
||||
|
||||
|
@ -106,7 +106,8 @@ static bool g_usbHostAvailable = false, g_usbSessionStarted = false, g_usbDetect
|
|||
static atomic_bool g_usbDetectionThreadCreated = false;
|
||||
|
||||
static u8 *g_usbTransferBuffer = NULL;
|
||||
static u64 g_usbTransferRemainingSize = 0;
|
||||
static u64 g_usbTransferRemainingSize = 0, g_usbTransferWrittenSize = 0;
|
||||
static u32 g_usbUrbId = 0;
|
||||
|
||||
/* Function prototypes. */
|
||||
|
||||
|
@ -119,7 +120,7 @@ static void usbEndSession(void);
|
|||
|
||||
NX_INLINE void usbPrepareCommandHeader(u32 cmd, u32 cmd_block_size);
|
||||
static u32 usbSendCommand(size_t cmd_size);
|
||||
NX_INLINE void usbLogStatusDetail(u32 status);
|
||||
static void usbLogStatusDetail(u32 status);
|
||||
|
||||
NX_INLINE bool usbAllocateTransferBuffer(void);
|
||||
NX_INLINE void usbFreeTransferBuffer(void);
|
||||
|
@ -135,8 +136,10 @@ static bool usbInitializeDeviceInterface1x(void);
|
|||
|
||||
NX_INLINE bool usbIsHostAvailable(void);
|
||||
|
||||
NX_INLINE bool usbRead(void *buf, size_t size);
|
||||
NX_INLINE bool usbWrite(void *buf, size_t size);
|
||||
NX_INLINE void usbSetZltPacket(bool enable);
|
||||
|
||||
NX_INLINE bool usbRead(void *buf, size_t size, bool reset_urb_id);
|
||||
NX_INLINE bool usbWrite(void *buf, size_t size, bool reset_urb_id);
|
||||
static bool usbTransferData(void *buf, size_t size, UsbDsEndpoint *endpoint);
|
||||
|
||||
bool usbInitialize(void)
|
||||
|
@ -259,6 +262,7 @@ bool usbSendFileProperties(u64 file_size, const char *filename)
|
|||
{
|
||||
ret = true;
|
||||
g_usbTransferRemainingSize = file_size;
|
||||
g_usbTransferWrittenSize = 0;
|
||||
} else {
|
||||
usbLogStatusDetail(status);
|
||||
}
|
||||
|
@ -275,9 +279,9 @@ bool usbSendFileData(void *data, u64 data_size)
|
|||
rwlockWriteLock(&g_usbDeviceLock);
|
||||
rwlockWriteLock(&(g_usbDeviceInterface.lock));
|
||||
|
||||
bool ret = false;
|
||||
void *buf = NULL;
|
||||
UsbStatus *cmd_status = NULL;
|
||||
bool ret = false, zlt_required = false;
|
||||
|
||||
if (!g_usbTransferBuffer || !g_usbDeviceInterfaceInitialized || !g_usbDeviceInterface.initialized || !g_usbHostAvailable || !g_usbSessionStarted || !g_usbTransferRemainingSize || !data || \
|
||||
!data_size || data_size > USB_TRANSFER_BUFFER_SIZE || data_size > g_usbTransferRemainingSize)
|
||||
|
@ -295,19 +299,47 @@ bool usbSendFileData(void *data, u64 data_size)
|
|||
memcpy(buf, data, data_size);
|
||||
}
|
||||
|
||||
if (!usbWrite(buf, data_size))
|
||||
/* Determine if we'll need to set a Zero Length Termination (ZLT) packet. */
|
||||
/* This is automatically handled by usbDsEndpoint_PostBufferAsync(), depending on the ZLT setting from the input (write) endpoint. */
|
||||
/* First, check if this is the last data chunk for this file. */
|
||||
if ((g_usbTransferRemainingSize - data_size) == 0)
|
||||
{
|
||||
LOGFILE("Failed to write 0x%lX bytes long file data chunk!", data_size);
|
||||
/* Enable ZLT if the last chunk size is aligned to the USB max packet size. */
|
||||
if (IS_ALIGNED(data_size, 0x40) || IS_ALIGNED(data_size, 0x200) || IS_ALIGNED(data_size, 0x400)) /* USB1, USB2 and USB3 max packet sizes, respectively. */
|
||||
{
|
||||
zlt_required = true;
|
||||
usbSetZltPacket(true);
|
||||
//LOGFILE("ZLT enabled. Last chunk size: 0x%lX bytes.", data_size);
|
||||
}
|
||||
} else {
|
||||
/* Disable ZLT if this is the first of multiple data chunks. */
|
||||
if (!g_usbTransferWrittenSize)
|
||||
{
|
||||
usbSetZltPacket(false);
|
||||
//LOGFILE("ZLT disabled (first chunk).");
|
||||
}
|
||||
}
|
||||
|
||||
/* Send data chunk. */
|
||||
/* Make sure to reset the URB ID if this is the first chunk. */
|
||||
if (!usbWrite(buf, data_size, !g_usbTransferWrittenSize))
|
||||
{
|
||||
LOGFILE("Failed to write 0x%lX bytes long file data chunk from offset 0x%lX! (total size: 0x%lX).", data_size, g_usbTransferWrittenSize, g_usbTransferRemainingSize + g_usbTransferWrittenSize);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
g_usbTransferRemainingSize -= data_size;
|
||||
g_usbTransferWrittenSize += data_size;
|
||||
|
||||
/* Check if this is the last chunk. */
|
||||
if (!g_usbTransferRemainingSize)
|
||||
{
|
||||
if (!usbRead(g_usbTransferBuffer, sizeof(UsbStatus)))
|
||||
/* Disable ZLT if it was previously enabled. */
|
||||
if (zlt_required) usbSetZltPacket(false);
|
||||
|
||||
/* Check response from host device. */
|
||||
if (!usbRead(g_usbTransferBuffer, sizeof(UsbStatus), true))
|
||||
{
|
||||
LOGFILE("Failed to read 0x%lX bytes long status block!", sizeof(UsbStatus));
|
||||
ret = false;
|
||||
|
@ -328,7 +360,7 @@ bool usbSendFileData(void *data, u64 data_size)
|
|||
}
|
||||
|
||||
end:
|
||||
if (!ret) g_usbTransferRemainingSize = 0;
|
||||
if (!ret) g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
|
||||
|
||||
rwlockWriteUnlock(&(g_usbDeviceInterface.lock));
|
||||
rwlockWriteUnlock(&g_usbDeviceLock);
|
||||
|
@ -336,6 +368,31 @@ end:
|
|||
return ret;
|
||||
}
|
||||
|
||||
void usbCancelFileTransfer(void)
|
||||
{
|
||||
rwlockWriteLock(&g_usbDeviceLock);
|
||||
rwlockWriteLock(&(g_usbDeviceInterface.lock));
|
||||
rwlockWriteLock(&(g_usbDeviceInterface.lock_in));
|
||||
|
||||
if (!g_usbTransferBuffer || !g_usbDeviceInterfaceInitialized || !g_usbDeviceInterface.initialized || !g_usbHostAvailable || !g_usbSessionStarted || !g_usbTransferRemainingSize) goto end;
|
||||
|
||||
/* Disable ZLT, just in case it was previously enabled. */
|
||||
usbDsEndpoint_SetZlt(g_usbDeviceInterface.endpoint_in, false);
|
||||
|
||||
/* Stall input (write) endpoint. */
|
||||
/* This will force the client to stop the current session, so a new one will have to be established. */
|
||||
usbDsEndpoint_Stall(g_usbDeviceInterface.endpoint_in);
|
||||
|
||||
/* Signal usermode USB timeout event. */
|
||||
/* This will "reset" the USB connection by making the background thread wait until a new session is established. */
|
||||
ueventSignal(&g_usbTimeoutEvent);
|
||||
|
||||
end:
|
||||
rwlockWriteUnlock(&(g_usbDeviceInterface.lock_in));
|
||||
rwlockWriteUnlock(&(g_usbDeviceInterface.lock));
|
||||
rwlockWriteUnlock(&g_usbDeviceLock);
|
||||
}
|
||||
|
||||
static bool usbCreateDetectionThread(void)
|
||||
{
|
||||
if (thrd_create(&g_usbDetectionThread, usbDetectionThreadFunc, NULL) != thrd_success)
|
||||
|
@ -383,7 +440,7 @@ static int usbDetectionThreadFunc(void *arg)
|
|||
/* Only proceed if we're dealing with a status change. */
|
||||
g_usbHostAvailable = usbIsHostAvailable();
|
||||
g_usbSessionStarted = false;
|
||||
g_usbTransferRemainingSize = 0;
|
||||
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
|
||||
|
||||
/* Start an USB session if we're connected to a host device. */
|
||||
/* This will essentially hang this thread and all other threads that call USB-related functions until: */
|
||||
|
@ -406,7 +463,7 @@ static int usbDetectionThreadFunc(void *arg)
|
|||
/* Close USB session if needed. */
|
||||
if (g_usbHostAvailable && g_usbSessionStarted) usbEndSession();
|
||||
g_usbHostAvailable = g_usbSessionStarted = g_usbDetectionThreadExitFlag = false;
|
||||
g_usbTransferRemainingSize = 0;
|
||||
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
|
||||
|
||||
rwlockWriteUnlock(&(g_usbDeviceInterface.lock));
|
||||
rwlockWriteUnlock(&g_usbDeviceLock);
|
||||
|
@ -454,7 +511,7 @@ static void usbEndSession(void)
|
|||
|
||||
usbPrepareCommandHeader(UsbCommandType_EndSession, 0);
|
||||
|
||||
if (!usbWrite(g_usbTransferBuffer, sizeof(UsbCommandHeader))) LOGFILE("Failed to send EndSession command!");
|
||||
if (!usbWrite(g_usbTransferBuffer, sizeof(UsbCommandHeader), true)) LOGFILE("Failed to send EndSession command!");
|
||||
}
|
||||
|
||||
NX_INLINE void usbPrepareCommandHeader(u32 cmd, u32 cmd_block_size)
|
||||
|
@ -478,14 +535,14 @@ static u32 usbSendCommand(size_t cmd_size)
|
|||
return UsbStatusType_InvalidCommandSize;
|
||||
}
|
||||
|
||||
if (!usbWrite(g_usbTransferBuffer, cmd_size))
|
||||
if (!usbWrite(g_usbTransferBuffer, cmd_size, true))
|
||||
{
|
||||
/* Log error message only if the USB session has been started, or if thread exit flag hasn't been enabled. */
|
||||
if (g_usbSessionStarted || !g_usbDetectionThreadExitFlag) LOGFILE("Failed to write 0x%lX bytes long block for type 0x%X command!", cmd_size, cmd);
|
||||
return UsbStatusType_WriteCommandFailed;
|
||||
}
|
||||
|
||||
if (!usbRead(g_usbTransferBuffer, sizeof(UsbStatus)))
|
||||
if (!usbRead(g_usbTransferBuffer, sizeof(UsbStatus), true))
|
||||
{
|
||||
/* Log error message only if the USB session has been started, or if thread exit flag hasn't been enabled. */
|
||||
if (g_usbSessionStarted || !g_usbDetectionThreadExitFlag) LOGFILE("Failed to read 0x%lX bytes long status block for type 0x%X command!", sizeof(UsbStatus), cmd);
|
||||
|
@ -503,7 +560,7 @@ static u32 usbSendCommand(size_t cmd_size)
|
|||
return cmd_status->status;
|
||||
}
|
||||
|
||||
NX_INLINE void usbLogStatusDetail(u32 status)
|
||||
static void usbLogStatusDetail(u32 status)
|
||||
{
|
||||
switch(status)
|
||||
{
|
||||
|
@ -625,7 +682,7 @@ static bool usbInitializeComms(void)
|
|||
}
|
||||
|
||||
/* Super Speed is USB 3.0. */
|
||||
/* Upgrade packet size to 512. */
|
||||
/* Upgrade packet size to 512 (1 << 9). */
|
||||
device_descriptor.bcdUSB = 0x0300;
|
||||
device_descriptor.bMaxPacketSize0 = 0x09;
|
||||
if (R_SUCCEEDED(rc))
|
||||
|
@ -984,19 +1041,27 @@ NX_INLINE bool usbIsHostAvailable(void)
|
|||
return (R_SUCCEEDED(rc) && state == 5);
|
||||
}
|
||||
|
||||
NX_INLINE bool usbRead(void *buf, u64 size)
|
||||
NX_INLINE void usbSetZltPacket(bool enable)
|
||||
{
|
||||
rwlockWriteLock(&(g_usbDeviceInterface.lock_in));
|
||||
usbDsEndpoint_SetZlt(g_usbDeviceInterface.endpoint_in, enable);
|
||||
rwlockWriteUnlock(&(g_usbDeviceInterface.lock_in));
|
||||
}
|
||||
|
||||
NX_INLINE bool usbRead(void *buf, u64 size, bool reset_urb_id)
|
||||
{
|
||||
rwlockWriteLock(&(g_usbDeviceInterface.lock_out));
|
||||
if (reset_urb_id) g_usbUrbId = 0;
|
||||
bool ret = usbTransferData(buf, size, g_usbDeviceInterface.endpoint_out);
|
||||
rwlockWriteUnlock(&(g_usbDeviceInterface.lock_out));
|
||||
return ret;
|
||||
}
|
||||
|
||||
NX_INLINE bool usbWrite(void *buf, u64 size)
|
||||
NX_INLINE bool usbWrite(void *buf, u64 size, bool reset_urb_id)
|
||||
{
|
||||
rwlockWriteLock(&(g_usbDeviceInterface.lock_in));
|
||||
if (reset_urb_id) g_usbUrbId = 0;
|
||||
bool ret = usbTransferData(buf, size, g_usbDeviceInterface.endpoint_in);
|
||||
if (ret) usbDsEndpoint_SetZlt(g_usbDeviceInterface.endpoint_in, true);
|
||||
rwlockWriteUnlock(&(g_usbDeviceInterface.lock_in));
|
||||
return ret;
|
||||
}
|
||||
|
@ -1015,17 +1080,16 @@ static bool usbTransferData(void *buf, u64 size, UsbDsEndpoint *endpoint)
|
|||
return false;
|
||||
}
|
||||
|
||||
u32 urb_id = 0;
|
||||
Result rc = 0;
|
||||
UsbDsReportData report_data = {0};
|
||||
u32 transferred_size = 0;
|
||||
bool thread_exit = false;
|
||||
|
||||
/* Start an USB transfer using the provided endpoint. */
|
||||
rc = usbDsEndpoint_PostBufferAsync(endpoint, buf, size, &urb_id);
|
||||
rc = usbDsEndpoint_PostBufferAsync(endpoint, buf, size, &g_usbUrbId);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOGFILE("usbDsEndpoint_PostBufferAsync failed! (0x%08X).", rc);
|
||||
LOGFILE("usbDsEndpoint_PostBufferAsync failed! (0x%08X) (URB ID %u).", rc, g_usbUrbId);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1065,27 +1129,27 @@ static bool usbTransferData(void *buf, u64 size, UsbDsEndpoint *endpoint)
|
|||
/* This will "reset" the USB connection by making the background thread wait until a new session is established. */
|
||||
if (g_usbSessionStarted) ueventSignal(&g_usbTimeoutEvent);
|
||||
|
||||
if (!thread_exit) LOGFILE("eventWait failed! (0x%08X).", rc);
|
||||
if (!thread_exit) LOGFILE("eventWait failed! (0x%08X) (URB ID %u).", rc, g_usbUrbId);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = usbDsEndpoint_GetReportData(endpoint, &report_data);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOGFILE("usbDsEndpoint_GetReportData failed! (0x%08X).", rc);
|
||||
LOGFILE("usbDsEndpoint_GetReportData failed! (0x%08X) (URB ID %u).", rc, g_usbUrbId);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = usbDsParseReportData(&report_data, urb_id, NULL, &transferred_size);
|
||||
rc = usbDsParseReportData(&report_data, g_usbUrbId, NULL, &transferred_size);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOGFILE("usbDsParseReportData failed! (0x%08X).", rc);
|
||||
LOGFILE("usbDsParseReportData failed! (0x%08X) (URB ID %u).", rc, g_usbUrbId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (transferred_size != size)
|
||||
{
|
||||
LOGFILE("USB transfer failed! Expected 0x%lX bytes, got 0x%X bytes.", size, transferred_size);
|
||||
LOGFILE("USB transfer failed! Expected 0x%lX bytes, got 0x%X bytes (URB ID %u).", size, transferred_size, g_usbUrbId);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,4 +50,8 @@ bool usbSendFileProperties(u64 file_size, const char *filename);
|
|||
/// Data chunk size must not exceed USB_TRANSFER_BUFFER_SIZE.
|
||||
bool usbSendFileData(void *data, u64 data_size);
|
||||
|
||||
/// Used to cancel an ongoing file transfer by stalling the input (write) USB endpoint.
|
||||
/// A new USB session must be established afterwards if USB communication with a host device is required.
|
||||
void usbCancelFileTransfer(void);
|
||||
|
||||
#endif /* __USB_H__ */
|
||||
|
|
Loading…
Reference in a new issue