1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-26 04:02:11 +00:00
nxdumptool/code_templates/usb_gc_dumper.c
Pablo Curiel 40fc21b5a3 Fix ProgramInfo and NSO issues.
XML generation confirmed to be working. The new algorithm faithfully reproduces the same output from legacy nxdumptool with much less overhead and memory usage.
2020-10-11 20:40:54 -04:00

765 lines
23 KiB
C

/*
* 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"
#include "title.h"
#include "crc32_fast.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;
u32 xci_crc, full_xci_crc;
} 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 void changeCrcOption(u32 idx);
static void read_thread_func(void *arg);
static void write_thread_func(void *arg);
/* Global variables. */
static bool g_appendKeyArea = false, g_keepCertificate = false, g_trimDump = false, g_calcCrc = 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
}
},
&(MenuElement){
.str = "calculate crc32",
.child_menu = NULL,
.task_func = NULL,
.element_options = &(MenuElementOption){
.selected = 0,
.options_func = &changeCrcOption,
.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;
utilsWriteLogBufferToLogFile("________________________________________________________________\r\n");
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;
u32 crc = 0;
char *filename = titleGenerateGameCardFileName(TitleFileNameConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars);
if (!dumpGameCardKeyArea(&gc_key_area) || !filename) goto end;
crc32FastCalculate(&gc_key_area, sizeof(GameCardKeyArea), &crc);
snprintf(path, MAX_ELEMENTS(path), "%s (Key Area) (%08X).bin", filename, crc);
if (!sendFileData(path, &gc_key_area, sizeof(GameCardKeyArea))) goto end;
printf("successfully sent key area as \"%s\"\n", path);
success = true;
end:
if (filename) free(filename);
utilsChangeHomeButtonBlockStatus(false);
consolePrint("press any button to continue");
utilsWaitForButtonPress(KEY_NONE);
return success;
}
static bool sendGameCardCertificateViaUsb(void)
{
waitForGameCardAndUsb();
utilsChangeHomeButtonBlockStatus(true);
FsGameCardCertificate gc_cert = {0};
bool success = false;
u32 crc = 0;
char *filename = titleGenerateGameCardFileName(TitleFileNameConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars);
if (!gamecardGetCertificate(&gc_cert) || !filename)
{
consolePrint("failed to get gamecard certificate\n");
goto end;
}
consolePrint("get gamecard certificate ok\n");
crc32FastCalculate(&gc_cert, sizeof(FsGameCardCertificate), &crc);
snprintf(path, MAX_ELEMENTS(path), "%s (Certificate) (%08X).bin", filename, crc);
if (!sendFileData(path, &gc_cert, sizeof(FsGameCardCertificate))) goto end;
printf("successfully sent certificate as \"%s\"\n", path);
success = true;
end:
if (filename) free(filename);
utilsChangeHomeButtonBlockStatus(false);
consolePrint("press any button to continue");
utilsWaitForButtonPress(KEY_NONE);
return success;
}
static bool sendGameCardImageViaUsb(void)
{
waitForGameCardAndUsb();
utilsChangeHomeButtonBlockStatus(true);
u64 gc_size = 0;
u32 key_area_crc = 0;
GameCardKeyArea gc_key_area = {0};
ThreadSharedData shared_data = {0};
Thread read_thread = {0}, write_thread = {0};
char *filename = NULL;
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");
filename = titleGenerateGameCardFileName(TitleFileNameConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars);
if (!filename)
{
consolePrint("failed to generate gamecard filename!\n");
goto end;
}
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;
if (g_calcCrc) crc32FastCalculate(&gc_key_area, sizeof(GameCardKeyArea), &key_area_crc);
shared_data.full_xci_crc = key_area_crc;
consolePrint("gamecard size (with key area): 0x%lX\n", gc_size);
}
snprintf(path, MAX_ELEMENTS(path), "%s (%s) (%s) (%s).xci", filename, 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");
utilsCreateThread(&read_thread, read_thread_func, &shared_data, 2);
utilsCreateThread(&write_thread, write_thread_func, &shared_data, 2);
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");
utilsJoinThread(&read_thread);
consolePrint("read_thread done: %lu\n", time(NULL));
utilsJoinThread(&write_thread);
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;
}
printf("process completed in %lu seconds\n", start);
success = true;
if (g_calcCrc)
{
if (g_appendKeyArea) printf("key area crc: %08X | ", key_area_crc);
printf("xci crc: %08X", shared_data.xci_crc);
if (g_appendKeyArea) printf(" | xci crc (with key area): %08X", shared_data.full_xci_crc);
printf("\n");
}
end:
if (shared_data.data) free(shared_data.data);
if (filename) free(filename);
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 void changeCrcOption(u32 idx)
{
g_calcCrc = (idx > 0);
}
static void 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;
goto end;
}
u8 *buf = malloc(BLOCK_SIZE);
if (!buf)
{
shared_data->read_error = true;
goto end;
}
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));
/* Update checksum */
if (g_calcCrc)
{
crc32FastCalculate(buf, blksize, &(shared_data->xci_crc));
if (g_appendKeyArea) crc32FastCalculate(buf, blksize, &(shared_data->full_xci_crc));
}
/* 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);
end:
threadExit();
}
static void write_thread_func(void *arg)
{
ThreadSharedData *shared_data = (ThreadSharedData*)arg;
if (!shared_data || !shared_data->data)
{
shared_data->write_error = true;
goto end;
}
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;
}
end:
threadExit();
}