1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-22 18:26:39 +00:00

New test app: system title NCA section dumper.

This commit is contained in:
Pablo Curiel 2020-07-29 17:02:21 -04:00
parent 5320260b4e
commit 7c4e7a4db0
14 changed files with 1438 additions and 389 deletions

View file

@ -0,0 +1,106 @@
/*
* 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");
}
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.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);
consolePrint("successfully saved key area to \"%s\"\n", path);
out2:
consolePrint("press any button to exit\n");
utilsWaitForButtonPress(KEY_NONE);
out:
utilsCloseResources();
consoleExit(NULL);
return ret;
}

View file

@ -0,0 +1,438 @@
/*
* 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 "nca.h"
#include "title.h"
#include "pfs.h"
#include "romfs.h"
#define BLOCK_SIZE 0x800000
#define OUTPATH "sdmc:/systitle_dumps"
static u8 *buf = NULL;
static FILE *filefd = NULL;
static char path[FS_MAX_PATH * 2] = {0};
static void consolePrint(const char *text, ...)
{
va_list v;
va_start(v, text);
vfprintf(stdout, text, v);
va_end(v);
consoleUpdate(NULL);
}
static void dumpPartitionFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
{
if (!buf || !info || !nca_fs_ctx) return;
u32 pfs_entry_count = 0;
PartitionFileSystemContext pfs_ctx = {0};
PartitionFileSystemEntry *pfs_entry = NULL;
char *pfs_entry_name = NULL;
size_t path_len = 0;
if (!pfsInitializeContext(&pfs_ctx, nca_fs_ctx))
{
consolePrint("pfs initialize ctx failed!\n");
goto end;
}
if (!(pfs_entry_count = pfsGetEntryCount(&pfs_ctx)))
{
consolePrint("pfs entry count is zero!\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);
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;
}
path[path_len] = '\0';
strcat(path, "/");
strcat(path, pfs_entry_name);
utilsReplaceIllegalCharacters(path + path_len + 1, true);
filefd = fopen(path, "wb");
if (!filefd)
{
consolePrint("failed to create \"%s\"!\n", path);
goto end;
}
consolePrint("dumping \"%s\"...\n", pfs_entry_name);
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);
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);
}
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;
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);
}
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;
}
}
int main(int argc, char *argv[])
{
(void)argc;
(void)argv;
int ret = 0;
LOGFILE(APP_TITLE " starting.");
consoleInit(NULL);
consolePrint("initializing...\n");
if (!utilsInitializeResources())
{
ret = -1;
goto out;
}
u32 app_count = 0;
TitleApplicationMetadata **app_metadata = NULL;
TitleInfo *cur_title_info = NULL;
u32 selected_idx = 0, menu = 0, page_size = 30, cur_page = 0;
u32 title_idx = 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)
{
consoleClear();
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");
if (menu >= 1) printf("selected title: %016lX - %s\n", app_metadata[title_idx]->title_id, app_metadata[title_idx]->lang_entry.name);
if (menu == 2) printf("selected content: %s\n", nca_id_str);
if (menu >= 1) printf("\n");
u32 max_val = (menu == 0 ? app_count : (menu == 1 ? cur_title_info->content_count : NCA_FS_HEADER_COUNT));
for(u32 i = cur_page; i < max_val; i++)
{
if (i >= (cur_page + page_size)) break;
printf("%s", i == selected_idx ? " -> " : " ");
if (menu == 0)
{
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("\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)
{
bool error = false;
if (menu == 0)
{
title_idx = selected_idx;
} else
if (menu == 1)
{
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();
dumpFsSection(cur_title_info, &(nca_ctx->fs_contexts[selected_idx]));
}
if (error || menu >= 3)
{
utilsSleep(3);
menu--;
} else {
selected_idx = cur_page = 0;
}
} else
if ((btn_down & KEY_DDOWN) || (btn_held & (KEY_LSTICK_DOWN | KEY_RSTICK_DOWN)))
{
selected_idx++;
if (selected_idx >= max_val)
{
if (btn_down & KEY_DDOWN)
{
selected_idx = cur_page = 0;
} else {
selected_idx = (max_val - 1);
}
} else
if (selected_idx >= (cur_page + page_size))
{
cur_page += page_size;
}
} else
if ((btn_down & KEY_DUP) || (btn_held & (KEY_LSTICK_UP | KEY_RSTICK_UP)))
{
selected_idx--;
if (selected_idx == UINT32_MAX)
{
if (btn_down & KEY_DUP)
{
selected_idx = (max_val - 1);
cur_page = (max_val - (max_val % page_size));
} else {
selected_idx = 0;
}
} else
if (selected_idx < cur_page)
{
cur_page -= page_size;
}
} else
if (btn_down & KEY_B)
{
menu--;
if (menu == UINT32_MAX)
{
break;
} else {
selected_idx = (menu == 0 ? title_idx : nca_idx);
cur_page = (selected_idx - (selected_idx % page_size));
}
}
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();
consoleExit(NULL);
return ret;
}

View file

@ -0,0 +1,491 @@
/*
* 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 "bktr.h"
#include "gamecard.h"
#include "usb.h"
#include "title.h"
#define TEST_BUF_SIZE 0x800000
static Mutex g_fileMutex = 0;
static CondVar g_readCondvar = 0, g_writeCondvar = 0;
typedef struct
{
//FILE *fileobj;
BktrContext *bktr_ctx;
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 consolePrint(const char *text, ...)
{
va_list v;
va_start(v, text);
vfprintf(stdout, text, v);
va_end(v);
consoleUpdate(NULL);
}
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(TEST_BUF_SIZE);
if (!buf)
{
shared_data->read_error = true;
return -2;
}
u64 file_table_offset = 0;
RomFileSystemFileEntry *file_entry = NULL;
char path[FS_MAX_PATH] = {0};
while(file_table_offset < shared_data->bktr_ctx->patch_romfs_ctx.file_table_size)
{
/* Check if the transfer has been cancelled by the user */
if (shared_data->transfer_cancelled)
{
condvarWakeAll(&g_writeCondvar);
break;
}
/* Retrieve RomFS file entry information */
shared_data->read_error = (!(file_entry = bktrGetFileEntryByOffset(shared_data->bktr_ctx, file_table_offset)) || \
!bktrGeneratePathFromFileEntry(shared_data->bktr_ctx, file_entry, path, FS_MAX_PATH, RomFileSystemPathIllegalCharReplaceType_IllegalFsChars));
if (shared_data->read_error)
{
condvarWakeAll(&g_writeCondvar);
break;
}
/* Wait until the previous file data chunk has been written */
mutexLock(&g_fileMutex);
if (shared_data->data_size && !shared_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex);
mutexUnlock(&g_fileMutex);
if (shared_data->write_error) break;
/* Send current file properties */
shared_data->read_error = !usbSendFileProperties(file_entry->size, path);
if (shared_data->read_error)
{
condvarWakeAll(&g_writeCondvar);
break;
}
for(u64 offset = 0, blksize = TEST_BUF_SIZE; offset < file_entry->size; offset += blksize)
{
if (blksize > (file_entry->size - offset)) blksize = (file_entry->size - offset);
/* Check if the transfer has been cancelled by the user */
if (shared_data->transfer_cancelled)
{
condvarWakeAll(&g_writeCondvar);
break;
}
/* Read current file data chunk */
shared_data->read_error = !bktrReadFileEntryData(shared_data->bktr_ctx, file_entry, buf, blksize, offset);
if (shared_data->read_error)
{
condvarWakeAll(&g_writeCondvar);
break;
}
/* Wait until the previous file 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);
}
if (shared_data->read_error || shared_data->write_error || shared_data->transfer_cancelled) break;
file_table_offset += ALIGN_UP(sizeof(RomFileSystemFileEntry) + file_entry->name_length, 4);
}
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;
}
//shared_data->write_error = (fwrite(shared_data->data, 1, shared_data->data_size, shared_data->fileobj) != shared_data->data_size);
/* 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 0;
}
int main(int argc, char *argv[])
{
(void)argc;
(void)argv;
int ret = 0;
LOGFILE(APP_TITLE " starting.");
consoleInit(NULL);
consolePrint("initializing...\n");
if (!utilsInitializeResources())
{
ret = -1;
goto out;
}
u32 app_count = 0, selected_idx = 0;
TitleApplicationMetadata **app_metadata = NULL;
TitleUserApplicationData user_app_data = {0};
u8 *buf = NULL;
NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL;
Ticket base_tik = {0}, update_tik = {0};
BktrContext bktr_ctx = {0};
ThreadSharedData shared_data = {0};
thrd_t read_thread, write_thread;
app_metadata = titleGetApplicationMetadataEntries(false, &app_count);
if (!app_metadata || !app_count)
{
consolePrint("app metadata failed\n");
goto out2;
}
consolePrint("app metadata succeeded\n");
buf = usbAllocatePageAlignedBuffer(TEST_BUF_SIZE);
if (!buf)
{
consolePrint("buf failed\n");
goto out2;
}
consolePrint("buf succeeded\n");
base_nca_ctx = calloc(1, sizeof(NcaContext));
if (!base_nca_ctx)
{
consolePrint("base nca ctx buf failed\n");
goto out2;
}
consolePrint("base nca ctx buf succeeded\n");
update_nca_ctx = calloc(1, sizeof(NcaContext));
if (!update_nca_ctx)
{
consolePrint("update nca ctx buf failed\n");
goto out2;
}
consolePrint("update nca ctx buf succeeded\n");
utilsSleep(1);
while(true)
{
consoleClear();
printf("select a base title with an available update.\nthe updated romfs will be dumped via usb.\npress b to cancel.\n\n");
for(u32 i = 0; i < app_count; i++) printf("%s%s (%016lX)\n", i == selected_idx ? " -> " : " ", app_metadata[i]->lang_entry.name, app_metadata[i]->title_id);
consoleUpdate(NULL);
u64 btn = 0;
while(true)
{
hidScanInput();
btn = utilsHidKeysAllDown();
if (btn) break;
if (titleRefreshGameCardTitleInfo())
{
free(app_metadata);
app_metadata = titleGetApplicationMetadataEntries(false, &app_count);
if (!app_metadata)
{
consolePrint("\napp metadata failed\n");
goto out2;
}
selected_idx = 0;
break;
}
}
if (btn & KEY_A)
{
if (!titleGetUserApplicationData(app_metadata[selected_idx]->title_id, &user_app_data) || !user_app_data.app_info || !user_app_data.patch_info)
{
consolePrint("\nthe selected title either doesn't have available base content or update content.\n");
utilsSleep(3);
continue;
}
break;
} else
if (btn & KEY_DOWN)
{
if ((selected_idx + 1) < app_count)
{
selected_idx++;
} else {
selected_idx = 0;
}
} else
if (btn & KEY_UP)
{
if (selected_idx == 0)
{
selected_idx = (app_count - 1);
} else {
selected_idx--;
}
} else
if (btn & KEY_B)
{
consolePrint("\nprocess cancelled.\n");
goto out2;
}
}
consoleClear();
consolePrint("selected title:\n%s (%016lX)\n\n", app_metadata[selected_idx]->lang_entry.name, app_metadata[selected_idx]->title_id);
if (!ncaInitializeContext(base_nca_ctx, user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Program, 0), &base_tik))
{
consolePrint("nca initialize base ctx failed\n");
goto out2;
}
if (!ncaInitializeContext(update_nca_ctx, user_app_data.patch_info->storage_id, (user_app_data.patch_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
titleGetContentInfoByTypeAndIdOffset(user_app_data.patch_info, NcmContentType_Program, 0), &update_tik))
{
consolePrint("nca initialize update ctx failed\n");
goto out2;
}
if (!bktrInitializeContext(&bktr_ctx, &(base_nca_ctx->fs_contexts[1]), &(update_nca_ctx->fs_contexts[1])))
{
consolePrint("bktr initialize ctx failed\n");
goto out2;
}
consolePrint("bktr initialize ctx succeeded\n");
shared_data.bktr_ctx = &bktr_ctx;
shared_data.data = buf;
shared_data.data_size = 0;
shared_data.data_written = 0;
bktrGetTotalDataSize(&bktr_ctx, &(shared_data.total_size));
consolePrint("waiting for usb connection... ");
time_t start = time(NULL);
bool usb_conn = false;
while(true)
{
time_t now = time(NULL);
if ((now - start) >= 10) break;
consolePrint("%lu ", now - start);
if ((usb_conn = usbIsReady())) break;
utilsSleep(1);
}
consolePrint("\n");
if (!usb_conn)
{
consolePrint("usb connection failed\n");
goto out2;
}
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 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 = (utilsHidKeysAllDown() & 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);
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 out2;
}
if (shared_data.transfer_cancelled)
{
consolePrint("process cancelled\n");
goto out2;
}
consolePrint("process completed in %lu seconds\n", start);
out2:
consolePrint("press any button to exit\n");
utilsWaitForButtonPress(KEY_NONE);
bktrFreeContext(&bktr_ctx);
if (update_nca_ctx) free(update_nca_ctx);
if (base_nca_ctx) free(base_nca_ctx);
if (buf) free(buf);
if (app_metadata) free(app_metadata);
out:
utilsCloseResources();
consoleExit(NULL);
return ret;
}

View file

@ -117,6 +117,16 @@ static MemoryLocation g_fsProgramMemory = {
.data_size = 0
};
static const char *g_gameCardHfsPartitionNames[] = {
[GameCardHashFileSystemPartitionType_Root] = "root",
[GameCardHashFileSystemPartitionType_Update] = "update",
[GameCardHashFileSystemPartitionType_Logo] = "logo",
[GameCardHashFileSystemPartitionType_Normal] = "normal",
[GameCardHashFileSystemPartitionType_Secure] = "secure",
[GameCardHashFileSystemPartitionType_Boot] = "boot",
[GameCardHashFileSystemPartitionType_Boot + 1] = "unknown"
};
/* Function prototypes. */
static bool gamecardCreateDetectionThread(void);
@ -359,6 +369,12 @@ bool gamecardGetBundledFirmwareUpdateVersion(u32 *out)
return ret;
}
const char *gamecardGetHashFileSystemPartitionName(u8 hfs_partition_type)
{
u8 idx = (hfs_partition_type > GameCardHashFileSystemPartitionType_Boot ? (GameCardHashFileSystemPartitionType_Boot + 1) : hfs_partition_type);
return g_gameCardHfsPartitionNames[idx];
}
bool gamecardGetEntryCountFromHashFileSystemPartition(u8 hfs_partition_type, u32 *out_count)
{
bool ret = false;
@ -1059,7 +1075,7 @@ static GameCardHashFileSystemHeader *gamecardGetHashFileSystemPartitionHeader(u8
if (hfs_partition_type != GameCardHashFileSystemPartitionType_Root)
{
if (gamecardGetHashFileSystemEntryIndexByName(fs_header, GAMECARD_HFS_PARTITION_NAME(hfs_partition_type), &hfs_partition_idx))
if (gamecardGetHashFileSystemEntryIndexByName(fs_header, gamecardGetHashFileSystemPartitionName(hfs_partition_type), &hfs_partition_idx))
{
fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsPartitions[hfs_partition_idx].header;
if (out_hfs_partition_idx) *out_hfs_partition_idx = hfs_partition_idx;

View file

@ -32,10 +32,6 @@
#define GAMECARD_UPDATE_TID (u64)0x0100000000000816
#define GAMECARD_HFS_PARTITION_NAME(x) ((x) == GameCardHashFileSystemPartitionType_Root ? "root" : ((x) == GameCardHashFileSystemPartitionType_Update ? "update" : \
((x) == GameCardHashFileSystemPartitionType_Logo ? "logo" : ((x) == GameCardHashFileSystemPartitionType_Normal ? "normal" : \
((x) == GameCardHashFileSystemPartitionType_Secure ? "secure" : ((x) == GameCardHashFileSystemPartitionType_Boot ? "boot" : "unknown"))))))
/// 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.
@ -222,6 +218,9 @@ bool gamecardGetTrimmedSize(u64 *out);
bool gamecardGetRomCapacity(u64 *out); ///< Not the same as gamecardGetTotalSize().
bool gamecardGetBundledFirmwareUpdateVersion(u32 *out);
/// Returns a pointer to a string holding the name of the provided hash file system partition type.
const char *gamecardGetHashFileSystemPartitionName(u8 hfs_partition_type);
/// Retrieves the entry count from a hash FS partition.
bool gamecardGetEntryCountFromHashFileSystemPartition(u8 hfs_partition_type, u32 *out_count);

View file

@ -19,28 +19,17 @@
*/
#include "utils.h"
#include "bktr.h"
#include "gamecard.h"
#include "usb.h"
#include "nca.h"
#include "title.h"
#include "pfs.h"
#include "romfs.h"
#define TEST_BUF_SIZE 0x800000
#define BLOCK_SIZE 0x800000
#define OUTPATH "sdmc:/systitle_dumps"
static Mutex g_fileMutex = 0;
static CondVar g_readCondvar = 0, g_writeCondvar = 0;
typedef struct
{
//FILE *fileobj;
BktrContext *bktr_ctx;
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 u8 *buf = NULL;
static FILE *filefd = NULL;
static char path[FS_MAX_PATH * 2] = {0};
static void consolePrint(const char *text, ...)
{
@ -51,147 +40,176 @@ static void consolePrint(const char *text, ...)
consoleUpdate(NULL);
}
static int read_thread_func(void *arg)
static void dumpPartitionFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
{
ThreadSharedData *shared_data = (ThreadSharedData*)arg;
if (!shared_data || !shared_data->data || !shared_data->total_size)
if (!buf || !info || !nca_fs_ctx) return;
u32 pfs_entry_count = 0;
PartitionFileSystemContext pfs_ctx = {0};
PartitionFileSystemEntry *pfs_entry = NULL;
char *pfs_entry_name = NULL;
size_t path_len = 0;
if (!pfsInitializeContext(&pfs_ctx, nca_fs_ctx))
{
shared_data->read_error = true;
return -1;
consolePrint("pfs initialize ctx failed!\n");
goto end;
}
u8 *buf = malloc(TEST_BUF_SIZE);
if (!buf)
if (!(pfs_entry_count = pfsGetEntryCount(&pfs_ctx)))
{
shared_data->read_error = true;
return -2;
consolePrint("pfs entry count is zero!\n");
goto end;
}
u64 file_table_offset = 0;
RomFileSystemFileEntry *file_entry = NULL;
char path[FS_MAX_PATH] = {0};
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(file_table_offset < shared_data->bktr_ctx->patch_romfs_ctx.file_table_size)
for(u32 i = 0; i < pfs_entry_count; i++)
{
/* Check if the transfer has been cancelled by the user */
if (shared_data->transfer_cancelled)
if (!(pfs_entry = pfsGetEntryByIndex(&pfs_ctx, i)) || !(pfs_entry_name = pfsGetEntryNameByIndex(&pfs_ctx, i)) || !strlen(pfs_entry_name))
{
condvarWakeAll(&g_writeCondvar);
break;
consolePrint("pfs get entry / get name #%u failed!\n", i);
goto end;
}
/* Retrieve RomFS file entry information */
shared_data->read_error = (!(file_entry = bktrGetFileEntryByOffset(shared_data->bktr_ctx, file_table_offset)) || \
!bktrGeneratePathFromFileEntry(shared_data->bktr_ctx, file_entry, path, FS_MAX_PATH, RomFileSystemPathIllegalCharReplaceType_IllegalFsChars));
if (shared_data->read_error)
path[path_len] = '\0';
strcat(path, "/");
strcat(path, pfs_entry_name);
utilsReplaceIllegalCharacters(path + path_len + 1, true);
filefd = fopen(path, "wb");
if (!filefd)
{
condvarWakeAll(&g_writeCondvar);
break;
consolePrint("failed to create \"%s\"!\n", path);
goto end;
}
/* Wait until the previous file data chunk has been written */
mutexLock(&g_fileMutex);
if (shared_data->data_size && !shared_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex);
mutexUnlock(&g_fileMutex);
if (shared_data->write_error) break;
consolePrint("dumping \"%s\"...\n", pfs_entry_name);
/* Send current file properties */
shared_data->read_error = !usbSendFileProperties(file_entry->size, path);
if (shared_data->read_error)
u64 blksize = BLOCK_SIZE;
for(u64 j = 0; j < pfs_entry->size; j += blksize)
{
condvarWakeAll(&g_writeCondvar);
break;
}
for(u64 offset = 0, blksize = TEST_BUF_SIZE; offset < file_entry->size; offset += blksize)
{
if (blksize > (file_entry->size - offset)) blksize = (file_entry->size - offset);
if (blksize > (pfs_entry->size - j)) blksize = (pfs_entry->size - j);
/* Check if the transfer has been cancelled by the user */
if (shared_data->transfer_cancelled)
if (!pfsReadEntryData(&pfs_ctx, pfs_entry, buf, blksize, j))
{
condvarWakeAll(&g_writeCondvar);
break;
consolePrint("failed to read 0x%lX block from offset 0x%lX!\n", blksize, j);
goto end;
}
/* Read current file data chunk */
shared_data->read_error = !bktrReadFileEntryData(shared_data->bktr_ctx, file_entry, buf, blksize, offset);
if (shared_data->read_error)
{
condvarWakeAll(&g_writeCondvar);
break;
}
/* Wait until the previous file 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);
fwrite(buf, 1, blksize, filefd);
}
if (shared_data->read_error || shared_data->write_error || shared_data->transfer_cancelled) break;
file_table_offset += ALIGN_UP(sizeof(RomFileSystemFileEntry) + file_entry->name_length, 4);
fclose(filefd);
filefd = NULL;
}
free(buf);
consolePrint("pfs dump complete\n");
return (shared_data->read_error ? -3 : 0);
end:
if (filefd)
{
fclose(filefd);
remove(path);
}
pfsFreeContext(&pfs_ctx);
}
static int write_thread_func(void *arg)
static void dumpRomFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
{
ThreadSharedData *shared_data = (ThreadSharedData*)arg;
if (!shared_data || !shared_data->data)
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;
if (!romfsInitializeContext(&romfs_ctx, nca_fs_ctx))
{
shared_data->write_error = true;
return -1;
consolePrint("romfs initialize ctx failed!\n");
goto end;
}
while(shared_data->data_written < shared_data->total_size)
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)
{
/* 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)
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))
{
mutexUnlock(&g_fileMutex);
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);
}
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;
}
//shared_data->write_error = (fwrite(shared_data->data, 1, shared_data->data_size, shared_data->fileobj) != shared_data->data_size);
/* 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 0;
}
int main(int argc, char *argv[])
@ -213,21 +231,18 @@ int main(int argc, char *argv[])
goto out;
}
u32 app_count = 0, selected_idx = 0;
u32 app_count = 0;
TitleApplicationMetadata **app_metadata = NULL;
TitleUserApplicationData user_app_data = {0};
TitleInfo *cur_title_info = NULL;
u8 *buf = NULL;
u32 selected_idx = 0, menu = 0, page_size = 30, cur_page = 0;
u32 title_idx = 0, nca_idx = 0;
char nca_id_str[0x21] = {0};
NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL;
Ticket base_tik = {0}, update_tik = {0};
NcaContext *nca_ctx = NULL;
Ticket tik = {0};
BktrContext bktr_ctx = {0};
ThreadSharedData shared_data = {0};
thrd_t read_thread, write_thread;
app_metadata = titleGetApplicationMetadataEntries(false, &app_count);
app_metadata = titleGetApplicationMetadataEntries(true, &app_count);
if (!app_metadata || !app_count)
{
consolePrint("app metadata failed\n");
@ -236,7 +251,7 @@ int main(int argc, char *argv[])
consolePrint("app metadata succeeded\n");
buf = usbAllocatePageAlignedBuffer(TEST_BUF_SIZE);
buf = malloc(BLOCK_SIZE);
if (!buf)
{
consolePrint("buf failed\n");
@ -245,235 +260,170 @@ int main(int argc, char *argv[])
consolePrint("buf succeeded\n");
base_nca_ctx = calloc(1, sizeof(NcaContext));
if (!base_nca_ctx)
nca_ctx = calloc(1, sizeof(NcaContext));
if (!nca_ctx)
{
consolePrint("base nca ctx buf failed\n");
consolePrint("nca ctx buf failed\n");
goto out2;
}
consolePrint("base nca ctx buf succeeded\n");
update_nca_ctx = calloc(1, sizeof(NcaContext));
if (!update_nca_ctx)
{
consolePrint("update nca ctx buf failed\n");
goto out2;
}
consolePrint("update nca ctx buf succeeded\n");
consolePrint("nca ctx buf succeeded\n");
utilsSleep(1);
while(true)
{
consoleClear();
printf("select a base title with an available update.\nthe updated romfs will be dumped via usb.\npress b to cancel.\n\n");
for(u32 i = 0; i < app_count; i++) printf("%s%s (%016lX)\n", i == selected_idx ? " -> " : " ", app_metadata[i]->lang_entry.name, app_metadata[i]->title_id);
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");
if (menu >= 1) printf("selected title: %016lX - %s\n", app_metadata[title_idx]->title_id, app_metadata[title_idx]->lang_entry.name);
if (menu == 2) printf("selected content: %s\n", nca_id_str);
if (menu >= 1) printf("\n");
u32 max_val = (menu == 0 ? app_count : (menu == 1 ? cur_title_info->content_count : NCA_FS_HEADER_COUNT));
for(u32 i = cur_page; i < max_val; i++)
{
if (i >= (cur_page + page_size)) break;
printf("%s", i == selected_idx ? " -> " : " ");
if (menu == 0)
{
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("\n");
consoleUpdate(NULL);
u64 btn = 0;
while(true)
u64 btn_down = 0, btn_held = 0;
while(!btn_down && !btn_held)
{
btn = utilsReadInput(UtilsInputType_Down);
if (btn) break;
hidScanInput();
btn_down = utilsHidKeysAllDown();
btn_held = utilsHidKeysAllHeld();
}
if (btn_down & KEY_A)
{
bool error = false;
if (titleRefreshGameCardTitleInfo())
if (menu == 0)
{
free(app_metadata);
app_metadata = titleGetApplicationMetadataEntries(false, &app_count);
if (!app_metadata)
title_idx = selected_idx;
} else
if (menu == 1)
{
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("\napp metadata failed\n");
goto out2;
consolePrint("failed to get title info\n");
error = true;
}
selected_idx = 0;
break;
}
}
if (btn & KEY_A)
{
if (!titleGetUserApplicationData(app_metadata[selected_idx]->title_id, &user_app_data) || !user_app_data.app_info || !user_app_data.patch_info)
} else
if (menu == 2)
{
consolePrint("\nthe selected title either doesn't have available base content or update content.\n");
utilsSleep(3);
continue;
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();
dumpFsSection(cur_title_info, &(nca_ctx->fs_contexts[selected_idx]));
}
break;
} else
if (btn & KEY_DOWN)
{
if ((selected_idx + 1) < app_count)
if (error || menu >= 3)
{
selected_idx++;
utilsSleep(3);
menu--;
} else {
selected_idx = 0;
selected_idx = cur_page = 0;
}
} else
if (btn & KEY_UP)
if ((btn_down & KEY_DDOWN) || (btn_held & (KEY_LSTICK_DOWN | KEY_RSTICK_DOWN)))
{
if (selected_idx == 0)
selected_idx++;
if (selected_idx >= max_val)
{
selected_idx = (app_count - 1);
} else {
selected_idx--;
if (btn_down & KEY_DDOWN)
{
selected_idx = cur_page = 0;
} else {
selected_idx = (max_val - 1);
}
} else
if (selected_idx >= (cur_page + page_size))
{
cur_page += page_size;
}
} else
if (btn & KEY_B)
if ((btn_down & KEY_DUP) || (btn_held & (KEY_LSTICK_UP | KEY_RSTICK_UP)))
{
consolePrint("\nprocess cancelled.\n");
goto out2;
}
}
consoleClear();
consolePrint("selected title:\n%s (%016lX)\n\n", app_metadata[selected_idx]->lang_entry.name, app_metadata[selected_idx]->title_id);
if (!ncaInitializeContext(base_nca_ctx, user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Program, 0), &base_tik))
{
consolePrint("nca initialize base ctx failed\n");
goto out2;
}
if (!ncaInitializeContext(update_nca_ctx, user_app_data.patch_info->storage_id, (user_app_data.patch_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
titleGetContentInfoByTypeAndIdOffset(user_app_data.patch_info, NcmContentType_Program, 0), &update_tik))
{
consolePrint("nca initialize update ctx failed\n");
goto out2;
}
if (!bktrInitializeContext(&bktr_ctx, &(base_nca_ctx->fs_contexts[1]), &(update_nca_ctx->fs_contexts[1])))
{
consolePrint("bktr initialize ctx failed\n");
goto out2;
}
consolePrint("bktr initialize ctx succeeded\n");
shared_data.bktr_ctx = &bktr_ctx;
shared_data.data = buf;
shared_data.data_size = 0;
shared_data.data_written = 0;
bktrGetTotalDataSize(&bktr_ctx, &(shared_data.total_size));
consolePrint("waiting for usb connection... ");
time_t start = time(NULL);
bool usb_conn = false;
while(true)
{
time_t now = time(NULL);
if ((now - start) >= 10) break;
consolePrint("%lu ", now - start);
if ((usb_conn = usbIsReady())) break;
utilsSleep(1);
}
consolePrint("\n");
if (!usb_conn)
{
consolePrint("usb connection failed\n");
goto out2;
}
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 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;
btn_cancel_cur_state = (utilsReadInput(UtilsInputType_Down) & 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)
selected_idx--;
if (selected_idx == UINT32_MAX)
{
if (btn_down & KEY_DUP)
{
selected_idx = (max_val - 1);
cur_page = (max_val - (max_val % page_size));
} else {
selected_idx = 0;
}
} else
if (selected_idx < cur_page)
{
cur_page -= page_size;
}
} else
if (btn_down & KEY_B)
{
menu--;
if (menu == UINT32_MAX)
{
mutexLock(&g_fileMutex);
shared_data.transfer_cancelled = true;
mutexUnlock(&g_fileMutex);
break;
} else {
selected_idx = (menu == 0 ? title_idx : nca_idx);
cur_page = (selected_idx - (selected_idx % page_size));
}
} 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);
if (btn_held & (KEY_LSTICK_DOWN | KEY_RSTICK_DOWN | KEY_LSTICK_UP | KEY_RSTICK_UP)) svcSleepThread(50000000); // 50 ms
}
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 out2;
}
if (shared_data.transfer_cancelled)
{
consolePrint("process cancelled\n");
goto out2;
}
consolePrint("process completed in %lu seconds\n", start);
out2:
consolePrint("press any button to exit\n");
utilsWaitForButtonPress(KEY_NONE);
if (menu != UINT32_MAX)
{
consolePrint("press any button to exit\n");
utilsWaitForButtonPress(KEY_NONE);
}
bktrFreeContext(&bktr_ctx);
if (update_nca_ctx) free(update_nca_ctx);
if (base_nca_ctx) free(base_nca_ctx);
if (nca_ctx) free(nca_ctx);
if (buf) free(buf);

View file

@ -38,6 +38,14 @@ static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = {
0xFF, 0x6B, 0x25, 0xEF, 0x9F, 0x96, 0x85, 0x28, 0x18, 0x9E, 0x76, 0xB0, 0x92, 0xF0, 0x6A, 0xCB
};
static const char *g_ncaFsSectionTypeNames[] = {
[NcaFsSectionType_PartitionFs] = "Partition FS",
[NcaFsSectionType_RomFs] = "RomFS",
[NcaFsSectionType_PatchRomFs] = "Patch RomFS [BKTR]",
[NcaFsSectionType_Nca0RomFs] = "NCA0 RomFS",
[NcaFsSectionType_Invalid] = "Invalid"
};
/* Function prototypes. */
NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info);
@ -158,15 +166,21 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
/* Parse sections. */
for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++)
{
/* Don't proceed if this NCA FS section isn't populated. */
if (!ncaIsFsInfoEntryValid(&(out->header.fs_info[i]))) continue;
/* Fill section context. */
out->fs_contexts[i].nca_ctx = out;
out->fs_contexts[i].section_num = i;
out->fs_contexts[i].section_type = NcaFsSectionType_Invalid; /* Placeholder. */
/* Don't proceed if this NCA FS section isn't populated. */
if (!ncaIsFsInfoEntryValid(&(out->header.fs_info[i]))) continue;
/* Calculate section offset and size. */
out->fs_contexts[i].section_offset = NCA_FS_SECTOR_OFFSET(out->header.fs_info[i].start_sector);
out->fs_contexts[i].section_size = (NCA_FS_SECTOR_OFFSET(out->header.fs_info[i].end_sector) - out->fs_contexts[i].section_offset);
out->fs_contexts[i].section_type = NcaFsSectionType_Invalid; /* Placeholder. */
/* Check if we're dealing with an invalid offset/size. */
if (out->fs_contexts[i].section_offset < sizeof(NcaHeader) || !out->fs_contexts[i].section_size || \
(out->fs_contexts[i].section_offset + out->fs_contexts[i].section_size) > out->content_size) continue;
/* Determine encryption type. */
out->fs_contexts[i].encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_AesXts : out->fs_contexts[i].header.encryption_type);
@ -187,11 +201,7 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
}
/* Check if we're dealing with an invalid encryption type value. */
if (out->fs_contexts[i].encryption_type == NcaEncryptionType_Auto || out->fs_contexts[i].encryption_type > NcaEncryptionType_AesCtrEx)
{
memset(&(out->fs_contexts[i]), 0, sizeof(NcaFsSectionContext));
continue;
}
if (out->fs_contexts[i].encryption_type == NcaEncryptionType_Auto || out->fs_contexts[i].encryption_type > NcaEncryptionType_AesCtrEx) continue;
/* Determine FS section type. */
if (out->fs_contexts[i].header.fs_type == NcaFsType_PartitionFs && out->fs_contexts[i].header.hash_type == NcaHashType_HierarchicalSha256)
@ -208,11 +218,7 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
}
/* Check if we're dealing with an invalid section type value. */
if (out->fs_contexts[i].section_type >= NcaFsSectionType_Invalid)
{
memset(&(out->fs_contexts[i]), 0, sizeof(NcaFsSectionContext));
continue;
}
if (out->fs_contexts[i].section_type >= NcaFsSectionType_Invalid) continue;
/* Initialize crypto data. */
if ((!out->rights_id_available || (out->rights_id_available && out->titlekey_retrieved)) && out->fs_contexts[i].encryption_type > NcaEncryptionType_None && \
@ -317,6 +323,12 @@ void ncaWriteHierarchicalIntegrityPatchToMemoryBuffer(NcaContext *ctx, NcaHierar
for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++) ncaWriteHashDataPatchToMemoryBuffer(ctx, &(patch->hash_level_patch[i]), buf, buf_size, buf_offset);
}
const char *ncaGetFsSectionTypeName(u8 section_type)
{
u8 idx = (section_type > NcaFsSectionType_Invalid ? NcaFsSectionType_Invalid : section_type);
return g_ncaFsSectionTypeNames[idx];
}
void ncaRemoveTitlekeyCrypto(NcaContext *ctx)
{
if (!ctx || ctx->content_size < NCA_FULL_HEADER_LENGTH || !ctx->rights_id_available || !ctx->titlekey_retrieved) return;

View file

@ -393,7 +393,8 @@ bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void
/// 'buf_offset' must hold the raw NCA offset where the data stored in 'buf' was read from.
void ncaWriteHierarchicalIntegrityPatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalIntegrityPatch *patch, void *buf, u64 buf_size, u64 buf_offset);
/// Returns a pointer to a string holding the name of the provided NCA FS section type.
const char *ncaGetFsSectionTypeName(u8 section_type);

View file

@ -167,7 +167,7 @@ bool pfsGetEntryIndexByName(PartitionFileSystemContext *ctx, const char *name, u
}
}
LOGFILE("Unable to find partition FS entry \"%s\"!", name);
//LOGFILE("Unable to find partition FS entry \"%s\"!", name);
return false;
}

View file

@ -47,6 +47,17 @@ static NcmContentStorage g_ncmStorageGameCard = {0}, g_ncmStorageEmmcSystem = {0
static TitleInfo *g_titleInfo = NULL;
static u32 g_titleInfoCount = 0, g_titleInfoGameCardStartIndex = 0, g_titleInfoGameCardCount = 0;
static const char *g_titleNcmContentTypeNames[] = {
[NcmContentType_Meta] = "Meta",
[NcmContentType_Program] = "Program",
[NcmContentType_Data] = "Data",
[NcmContentType_Control] = "Control",
[NcmContentType_HtmlDocument] = "HtmlDocument",
[NcmContentType_LegalInformation] = "LegalInformation",
[NcmContentType_DeltaFragment] = "DeltaFragment",
[NcmContentType_DeltaFragment + 1] = "Unknown"
};
/* Info retrieved from https://switchbrew.org/wiki/Title_list. */
/* Titles bundled with the kernel are excluded. */
static const SystemTitleName g_systemTitles[] = {
@ -732,7 +743,11 @@ end:
return success;
}
const char *titleGetNcmContentTypeName(u8 content_type)
{
u8 idx = (content_type > NcmContentType_DeltaFragment ? (NcmContentType_DeltaFragment + 1) : content_type);
return g_titleNcmContentTypeNames[idx];
}
@ -1153,7 +1168,7 @@ static bool titleRetrieveContentMetaKeysFromDatabase(u8 storage_id)
/* Get a full list of all titles available in this storage. */
/* Meta type '0' means all title types will be retrieved. */
rc = ncmContentMetaDatabaseList(ncm_db, (s32*)&total, (s32*)&written, meta_keys, 1, 0, 0, 0, -1, NcmContentInstallType_Full);
rc = ncmContentMetaDatabaseList(ncm_db, (s32*)&total, (s32*)&written, meta_keys, 1, 0, 0, 0, UINT64_MAX, NcmContentInstallType_Full);
if (R_FAILED(rc))
{
LOGFILE("ncmContentMetaDatabaseList failed! (0x%08X) (first entry).", rc);
@ -1186,7 +1201,7 @@ static bool titleRetrieveContentMetaKeysFromDatabase(u8 storage_id)
meta_keys_tmp = NULL;
/* Issue call again. */
rc = ncmContentMetaDatabaseList(ncm_db, (s32*)&total, (s32*)&written, meta_keys, (s32)total, 0, 0, 0, -1, NcmContentInstallType_Full);
rc = ncmContentMetaDatabaseList(ncm_db, (s32*)&total, (s32*)&written, meta_keys, (s32)total, 0, 0, 0, UINT64_MAX, NcmContentInstallType_Full);
if (R_FAILED(rc))
{
LOGFILE("ncmContentMetaDatabaseList failed! (0x%08X) (%u %s).", rc, total, total > 1 ? "entries" : "entry");

View file

@ -87,12 +87,12 @@ NcmContentStorage *titleGetNcmStorageByStorageId(u8 storage_id);
bool titleRefreshGameCardTitleInfo(void);
/// Returns a pointer to a dynamically allocated buffer of pointers to TitleApplicationMetadata entries, as well as their count. The allocated buffer must be freed by the calling function.
/// If 'is_system' is true, TitleApplicationMetadata entries from available system titles will be returned.
/// Otherwise, TitleApplicationMetadata entries from user applications with available content data will be returned.
/// If 'is_system' is true, TitleApplicationMetadata entries from available system titles (NcmStorageId_BuiltInSystem) will be returned.
/// Otherwise, TitleApplicationMetadata entries from user applications with available content data (NcmStorageId_Any) will be returned.
/// Returns NULL if an error occurs.
TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count);
/// Retrieves a pointer to a TitleInfo entry with a matching storage ID and title ID.
/// Returns a pointer to a TitleInfo entry with a matching storage ID and title ID.
/// If NcmStorageId_Any is used, the first entry with a matching title ID is returned.
/// Returns NULL if an error occurs.
TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id);
@ -100,6 +100,16 @@ TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id);
/// Populates a TitleUserApplicationData element using an user application ID.
bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out);
/// Returns a pointer to a string holding the name of the provided ncm content type.
const char *titleGetNcmContentTypeName(u8 content_type);

View file

@ -29,7 +29,7 @@
#define USB_CMD_HEADER_MAGIC 0x4E584454 /* "NXDT". */
#define USB_TRANSFER_ALIGNMENT 0x1000 /* 4 KiB. */
#define USB_TRANSFER_TIMEOUT 3 /* 3 seconds. */
#define USB_TRANSFER_TIMEOUT 6 /* 6 seconds. */
/* Type definitions. */

View file

@ -57,9 +57,6 @@ static const u32 g_sizeSuffixesCount = MAX_ELEMENTS(g_sizeSuffixes);
/* Function prototypes. */
static u64 utilsHidKeysAllDown(void);
static u64 utilsHidKeysAllHeld(void);
static bool utilsMountEmmcBisSystemPartitionStorage(void);
static void utilsUnmountEmmcBisSystemPartitionStorage(void);
@ -200,13 +197,24 @@ void utilsCloseResources(void)
mutexUnlock(&g_resourcesMutex);
}
u64 utilsReadInput(u8 input_type)
u64 utilsHidKeysAllDown(void)
{
if (input_type != UtilsInputType_Down && input_type != UtilsInputType_Held) return 0;
u8 controller;
u64 keys_down = 0;
hidScanInput();
for(controller = 0; controller < (u8)CONTROLLER_P1_AUTO; controller++) keys_down |= hidKeysDown((HidControllerID)controller);
return (input_type == UtilsInputType_Down ? utilsHidKeysAllDown() : utilsHidKeysAllHeld());
return keys_down;
}
u64 utilsHidKeysAllHeld(void)
{
u8 controller;
u64 keys_held = 0;
for(controller = 0; controller < (u8)CONTROLLER_P1_AUTO; controller++) keys_held |= hidKeysHeld((HidControllerID)controller);
return keys_held;
}
void utilsWaitForButtonPress(u64 flag)
@ -221,7 +229,8 @@ void utilsWaitForButtonPress(u64 flag)
while(appletMainLoop())
{
keys_down = utilsReadInput(UtilsInputType_Down);
hidScanInput();
keys_down = utilsHidKeysAllDown();
if (keys_down & flag) break;
}
}
@ -434,6 +443,29 @@ bool utilsCreateConcatenationFile(const char *path)
return true;
}
void utilsCreateDirectoryTree(const char *path, bool create_last_element)
{
char *ptr = NULL, *tmp = NULL;
size_t path_len = 0;
if (!path || !(path_len = strlen(path))) return;
tmp = calloc(path_len + 1, sizeof(char));
if (!tmp) return;
ptr = strchr(path, '/');
while(ptr)
{
sprintf(tmp, "%.*s", (int)(ptr - path), path);
mkdir(tmp, 0777);
ptr = strchr(++ptr, '/');
}
if (create_last_element) mkdir(path, 0777);
free(tmp);
}
bool utilsAppletModeCheck(void)
{
return (g_programAppletType != AppletType_Application && g_programAppletType != AppletType_SystemApplication);
@ -476,26 +508,6 @@ void utilsOverclockSystem(bool overclock)
servicesChangeHardwareClockRates(cpuClkRate, memClkRate);
}
static u64 utilsHidKeysAllDown(void)
{
u8 controller;
u64 keys_down = 0;
for(controller = 0; controller < (u8)CONTROLLER_P1_AUTO; controller++) keys_down |= hidKeysDown((HidControllerID)controller);
return keys_down;
}
static u64 utilsHidKeysAllHeld(void)
{
u8 controller;
u64 keys_held = 0;
for(controller = 0; controller < (u8)CONTROLLER_P1_AUTO; controller++) keys_held |= hidKeysHeld((HidControllerID)controller);
return keys_held;
}
static bool utilsMountEmmcBisSystemPartitionStorage(void)
{
Result rc = 0;

View file

@ -66,12 +66,6 @@
typedef enum {
UtilsInputType_Down = 0,
UtilsInputType_Held = 1
} UtilsInputType;
typedef enum {
UtilsCustomFirmwareType_Unknown = 0,
UtilsCustomFirmwareType_Atmosphere = 1,
@ -82,7 +76,10 @@ typedef enum {
bool utilsInitializeResources(void);
void utilsCloseResources(void);
u64 utilsReadInput(u8 input_type);
/// hidScanInput() must be called before any of these functions.
u64 utilsHidKeysAllDown(void);
u64 utilsHidKeysAllHeld(void);
void utilsWaitForButtonPress(u64 flag);
void utilsWriteMessageToLogFile(const char *func_name, const char *fmt, ...);
@ -105,6 +102,8 @@ bool utilsCheckIfFileExists(const char *path);
bool utilsCreateConcatenationFile(const char *path);
void utilsCreateDirectoryTree(const char *path, bool create_last_element);
bool utilsAppletModeCheck(void);
void utilsChangeHomeButtonBlockStatus(bool block);