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:
parent
5320260b4e
commit
7c4e7a4db0
14 changed files with 1438 additions and 389 deletions
106
code_templates/gc_key_area.c
Normal file
106
code_templates/gc_key_area.c
Normal 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;
|
||||
}
|
438
code_templates/system_title_dumper.c
Normal file
438
code_templates/system_title_dumper.c
Normal 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;
|
||||
}
|
491
code_templates/threaded_usb_bktr_dumper.c
Normal file
491
code_templates/threaded_usb_bktr_dumper.c
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
602
source/main.c
602
source/main.c
|
@ -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);
|
||||
|
||||
|
|
40
source/nca.c
40
source/nca.c
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue