Codebase cleanup.

Remove legacy code and trailing whitespace from all files.
This commit is contained in:
Pablo Curiel 2022-07-05 03:04:28 +02:00
parent 91dc20b7f3
commit 942a407247
88 changed files with 4680 additions and 19299 deletions

1
.gitignore vendored
View file

@ -18,4 +18,3 @@ host/nxdumptool
*.exe
main.cpp
/code_templates/tmp/*
/galgun

View file

@ -17,26 +17,26 @@ make clean_all
for f in ./code_templates/*.c; do
basename="$(basename "$f")"
filename="${basename%.*}"
if [[ $filename == "dump_title_infos" ]]; then
continue
fi
echo $filename
rm -f ./source/main.c
cp $f ./source/main.c
cp ./romfs/icon/nxdumptool.jpg ./romfs/icon/$filename.jpg
make BUILD_TYPE="$filename" -j$(nproc)
rm -f ./romfs/icon/$filename.jpg
mkdir ./code_templates/tmp/$filename
cp ./$filename.nro ./code_templates/tmp/$filename/$filename.nro
cp ./$filename.elf ./code_templates/tmp/$filename/$filename.elf
make BUILD_TYPE="$filename" clean
done

View file

@ -1,10 +1,10 @@
if (g_titleInfo && g_titleInfoCount)
{
mkdir("sdmc:/records", 0777);
FILE *title_infos_txt = NULL, *icon_jpg = NULL;
char icon_path[FS_MAX_PATH] = {0};
title_infos_txt = fopen("sdmc:/records/title_infos.txt", "wb");
if (title_infos_txt)
{
@ -18,39 +18,39 @@
fprintf(title_infos_txt, "Type: 0x%02X\r\n", g_titleInfo[i].meta_key.type);
fprintf(title_infos_txt, "Install Type: 0x%02X\r\n", g_titleInfo[i].meta_key.install_type);
fprintf(title_infos_txt, "Title Size: %s (0x%lX)\r\n", g_titleInfo[i].size_str, g_titleInfo[i].size);
fprintf(title_infos_txt, "Content Count: %u\r\n", g_titleInfo[i].content_count);
for(u32 j = 0; j < g_titleInfo[i].content_count; j++)
{
char content_id_str[SHA256_HASH_SIZE + 1] = {0};
utilsGenerateHexStringFromData(content_id_str, sizeof(content_id_str), g_titleInfo[i].content_infos[j].content_id.c, sizeof(g_titleInfo[i].content_infos[j].content_id.c), false);
u64 content_size = 0;
titleConvertNcmContentSizeToU64(g_titleInfo[i].content_infos[j].size, &content_size);
char content_size_str[32] = {0};
utilsGenerateFormattedSizeString(content_size, content_size_str, sizeof(content_size_str));
fprintf(title_infos_txt, " Content #%u:\r\n", j + 1);
fprintf(title_infos_txt, " Content ID: %s\r\n", content_id_str);
fprintf(title_infos_txt, " Content Size: %s (0x%lX)\r\n", content_size_str, content_size);
fprintf(title_infos_txt, " Content Type: 0x%02X\r\n", g_titleInfo[i].content_infos[j].content_type);
fprintf(title_infos_txt, " ID Offset: 0x%02X\r\n", g_titleInfo[i].content_infos[j].id_offset);
}
if (g_titleInfo[i].app_metadata)
{
TitleApplicationMetadata *app_metadata = g_titleInfo[i].app_metadata;
if (strlen(app_metadata->lang_entry.name)) fprintf(title_infos_txt, "Name: %s\r\n", app_metadata->lang_entry.name);
if (strlen(app_metadata->lang_entry.author)) fprintf(title_infos_txt, "Author: %s\r\n", app_metadata->lang_entry.author);
if (g_titleInfo[i].meta_key.type == NcmContentMetaType_Application && app_metadata->icon_size && app_metadata->icon)
{
fprintf(title_infos_txt, "JPEG Icon Size: 0x%X\r\n", app_metadata->icon_size);
sprintf(icon_path, "sdmc:/records/%016lX.jpg", app_metadata->title_id);
icon_jpg = fopen(icon_path, "wb");
if (icon_jpg)
{
@ -61,18 +61,18 @@
}
}
}
if (g_titleInfo[i].meta_key.type == NcmContentMetaType_Patch || g_titleInfo[i].meta_key.type == NcmContentMetaType_AddOnContent)
{
if (g_titleInfo[i].previous) fprintf(title_infos_txt, "Previous %s ID: %016lX\r\n", g_titleInfo[i].meta_key.type == NcmContentMetaType_Patch ? "Patch" : "AOC", g_titleInfo[i].previous->meta_key.id);
if (g_titleInfo[i].next) fprintf(title_infos_txt, "Next %s ID: %016lX\r\n", g_titleInfo[i].meta_key.type == NcmContentMetaType_Patch ? "Patch" : "AOC", g_titleInfo[i].next->meta_key.id);
}
fprintf(title_infos_txt, "\r\n");
fflush(title_infos_txt);
}
fclose(title_infos_txt);
title_infos_txt = NULL;
utilsCommitSdCardFileSystemChanges();

View file

@ -246,7 +246,7 @@ static void utilsWaitForButtonPress(u64 flag)
/* Don't consider stick movement as button inputs. */
if (!flag) flag = ~(HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \
HidNpadButton_StickRUp | HidNpadButton_StickRDown);
while(appletMainLoop())
{
utilsScanPads();
@ -257,44 +257,44 @@ static void utilsWaitForButtonPress(u64 flag)
int main(int argc, char *argv[])
{
int ret = 0;
Menu *cur_menu = &g_rootMenu;
u32 element_count = menuGetElementCount(cur_menu), page_size = 30;
if (!utilsInitializeResources(argc, (const char**)argv))
{
ret = -1;
goto out;
}
/* Configure input. */
/* Up to 8 different, full controller inputs. */
/* Individual Joy-Cons not supported. */
padConfigureInput(8, HidNpadStyleSet_NpadFullCtrl);
padInitializeWithMask(&g_padState, 0x1000000FFUL);
consoleInit(NULL);
chdir(DEVOPTAB_SDMC_DEVICE GAMECARD_PATH);
while(appletMainLoop())
{
consoleClear();
printf("\npress b to %s.\n\n", cur_menu->parent ? "go back" : "exit");
u32 limit = (cur_menu->scroll + page_size);
MenuElement *selected_element = cur_menu->elements[cur_menu->selected];
MenuElementOption *selected_element_options = selected_element->element_options;
for(u32 i = cur_menu->scroll; cur_menu->elements[i] && i < element_count; i++)
{
if (i >= limit) break;
MenuElement *cur_element = cur_menu->elements[i];
MenuElementOption *cur_options = cur_menu->elements[i]->element_options;
printf("%s%s", i == cur_menu->selected ? " -> " : " ", cur_element->str);
if (cur_options)
{
printf(": ");
@ -302,13 +302,13 @@ int main(int argc, char *argv[])
printf("%s", cur_options->options[cur_options->selected]);
if (cur_options->options[cur_options->selected + 1]) printf(" >");
}
printf("\n");
}
printf("\n");
consoleUpdate(NULL);
u64 btn_down = 0, btn_held = 0;
while((g_appletStatus = appletMainLoop()))
{
@ -317,13 +317,13 @@ int main(int argc, char *argv[])
btn_held = utilsGetButtonsHeld();
if (btn_down || btn_held) break;
}
if (!g_appletStatus) break;
if (btn_down & HidNpadButton_A)
{
Menu *child_menu = (Menu*)selected_element->child_menu;
if (child_menu)
{
child_menu->parent = cur_menu;
@ -338,18 +338,18 @@ int main(int argc, char *argv[])
if (g_appletStatus) continue;
break;
}
/* Wait for USB session. */
if (g_useUsbHost && !waitForUsb()) break;
/* Generate dump text. */
generateDumpTxt();
/* Run task. */
utilsSetLongRunningProcessState(true);
if (selected_element->task_func()) saveDumpTxt();
utilsSetLongRunningProcessState(false);
/* Display prompt. */
consolePrint("press any button to continue");
utilsWaitForButtonPress(0);
@ -358,7 +358,7 @@ int main(int argc, char *argv[])
if ((btn_down & HidNpadButton_Down) || (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown)))
{
cur_menu->selected++;
if (!cur_menu->elements[cur_menu->selected])
{
if (btn_down & HidNpadButton_Down)
@ -377,7 +377,7 @@ int main(int argc, char *argv[])
if ((btn_down & HidNpadButton_Up) || (btn_held & (HidNpadButton_StickLUp | HidNpadButton_StickRUp)))
{
cur_menu->selected--;
if (cur_menu->selected == UINT32_MAX)
{
if (btn_down & HidNpadButton_Up)
@ -408,19 +408,19 @@ int main(int argc, char *argv[])
if (btn_down & HidNpadButton_B)
{
if (!cur_menu->parent) break;
cur_menu = cur_menu->parent;
element_count = menuGetElementCount(cur_menu);
}
if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp)) svcSleepThread(50000000); // 50 ms
}
out:
utilsCloseResources();
consoleExit(NULL);
return ret;
}
@ -436,7 +436,7 @@ static void consolePrint(const char *text, ...)
static u32 menuGetElementCount(const Menu *menu)
{
if (!menu || !menu->elements || !menu->elements[0]) return 0;
u32 cnt;
for(cnt = 0; menu->elements[cnt]; cnt++);
return cnt;
@ -446,17 +446,17 @@ static bool waitForGameCard(void)
{
consoleClear();
consolePrint("waiting for gamecard...\n");
u8 status = GameCardStatus_NotInserted;
while((g_appletStatus = appletMainLoop()))
{
status = gamecardGetStatus();
if (status > GameCardStatus_Processing) break;
}
if (!g_appletStatus) return false;
switch(status)
{
case GameCardStatus_NoGameCardPatchEnabled:
@ -471,43 +471,43 @@ static bool waitForGameCard(void)
default:
break;
}
if (status != GameCardStatus_InsertedAndInfoLoaded)
{
consolePrint("press any button\n");
utilsWaitForButtonPress(0);
return false;
}
return true;
}
static bool waitForUsb(void)
{
if (usbIsReady()) return true;
consolePrint("waiting for usb session...\n");
while((g_appletStatus = appletMainLoop()))
{
if (usbIsReady()) break;
}
return g_appletStatus;
}
static void generateDumpTxt(void)
{
*txt_info = '\0';
struct tm ts = {0};
time_t now = time(NULL);
/* Get UTC time. */
gmtime_r(&now, &ts);
ts.tm_year += 1900;
ts.tm_mon++;
/* Generate dump text. */
snprintf(txt_info, MAX_ELEMENTS(txt_info), "tool: nxdumptool\r\n" \
"version: " APP_VERSION "\r\n" \
@ -525,7 +525,7 @@ static bool saveFileData(const char *path, void *data, size_t data_size)
consolePrint("invalid parameters to save file data!\n");
return false;
}
if (g_useUsbHost)
{
if (!usbSendFilePropertiesCommon(data_size, path))
@ -533,7 +533,7 @@ static bool saveFileData(const char *path, void *data, size_t data_size)
consolePrint("failed to send file properties for \"%s\"!\n", path);
return false;
}
if (!usbSendFileData(data, data_size))
{
consolePrint("failed to send file data for \"%s\"!\n", path);
@ -546,50 +546,50 @@ static bool saveFileData(const char *path, void *data, size_t data_size)
consolePrint("failed to open \"%s\" for writing!\n", path);
return false;
}
size_t ret = fwrite(data, 1, data_size, fp);
fclose(fp);
if (ret != data_size)
{
consolePrint("failed to write 0x%lX byte(s) to \"%s\"! (%d)\n", data_size, path, errno);
remove(path);
}
}
return true;
}
static bool saveDumpTxt(void)
{
if (!*path || !*txt_info) return true;
path[strlen(path) - 3] = '\0';
strcat(path, "txt");
return saveFileData(path, txt_info, strlen(txt_info));
}
static char *generateOutputFileName(const char *extension)
{
char *filename = NULL, *output = NULL;
if (!extension || !*extension || !(filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, g_useUsbHost ? TitleFileNameIllegalCharReplaceType_IllegalFsChars : TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly)))
{
consolePrint("failed to get gamecard filename!\n");
return NULL;
}
output = utilsGeneratePath(NULL, filename, extension);
free(filename);
if (output)
{
snprintf(path, MAX_ELEMENTS(path), "%s", output);
} else {
consolePrint("failed to generate output filename!\n");
}
return output;
}
@ -600,13 +600,13 @@ static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out)
consolePrint("invalid parameters to dump gamecard security information!\n");
return false;
}
if (!gamecardGetSecurityInformation(out))
{
consolePrint("failed to get gamecard security information\n");
return false;
}
consolePrint("get gamecard security information ok\n");
return true;
}
@ -617,23 +617,23 @@ static bool saveGameCardSpecificData(void)
bool success = false;
u32 crc = 0;
char *filename = NULL;
if (!dumpGameCardSecurityInformation(&gc_security_information)) goto end;
crc = crc32Calculate(&(gc_security_information.specific_data), sizeof(GameCardSpecificData));
snprintf(path, MAX_ELEMENTS(path), " (Specific Data) (%08X).bin", crc);
filename = generateOutputFileName(path);
if (!filename) goto end;
if (!saveFileData(filename, &(gc_security_information.specific_data), sizeof(GameCardSpecificData))) goto end;
printf("successfully saved specific data as \"%s\"\n", filename);
success = true;
end:
if (filename) free(filename);
return success;
}
@ -643,29 +643,29 @@ static bool saveGameCardCertificate(void)
bool success = false;
u32 crc = 0;
char *filename = NULL;
if (!gamecardGetCertificate(&gc_cert))
{
consolePrint("failed to get gamecard certificate\n");
goto end;
}
consolePrint("get gamecard certificate ok\n");
crc = crc32Calculate(&gc_cert, sizeof(FsGameCardCertificate));
snprintf(path, MAX_ELEMENTS(path), " (Certificate) (%08X).bin", crc);
filename = generateOutputFileName(path);
if (!filename) goto end;
if (!saveFileData(filename, &gc_cert, sizeof(FsGameCardCertificate))) goto end;
printf("successfully saved certificate as \"%s\"\n", filename);
success = true;
end:
if (filename) free(filename);
return success;
}
@ -675,23 +675,23 @@ static bool saveGameCardInitialData(void)
bool success = false;
u32 crc = 0;
char *filename = NULL;
if (!dumpGameCardSecurityInformation(&gc_security_information)) goto end;
crc = crc32Calculate(&(gc_security_information.initial_data), sizeof(GameCardInitialData));
snprintf(path, MAX_ELEMENTS(path), " (Initial Data) (%08X).bin", crc);
filename = generateOutputFileName(path);
if (!filename) goto end;
if (!saveFileData(filename, &(gc_security_information.initial_data), sizeof(GameCardInitialData))) goto end;
printf("successfully saved initial data as \"%s\"\n", filename);
success = true;
end:
if (filename) free(filename);
return success;
}
@ -701,81 +701,81 @@ static bool saveGameCardIdSet(void)
bool success = false;
u32 crc = 0;
char *filename = NULL;
if (!gamecardGetIdSet(&id_set)) goto end;
crc = crc32Calculate(&id_set, sizeof(FsGameCardIdSet));
snprintf(path, MAX_ELEMENTS(path), " (Card ID Set) (%08X).bin", crc);
filename = generateOutputFileName(path);
if (!filename) goto end;
if (!saveFileData(filename, &id_set, sizeof(FsGameCardIdSet))) goto end;
printf("successfully saved gamecard id set as \"%s\"\n", filename);
success = true;
end:
if (filename) free(filename);
return success;
}
static bool saveGameCardImage(void)
{
u64 gc_size = 0;
u32 key_area_crc = 0;
GameCardKeyArea gc_key_area = {0};
GameCardSecurityInformation gc_security_information = {0};
ThreadSharedData shared_data = {0};
Thread read_thread = {0}, write_thread = {0};
char *filename = NULL;
bool success = false;
consolePrint("gamecard image dump\nappend key area: %s | keep certificate: %s | trim dump: %s | calculate crc32: %s\n\n", g_appendKeyArea ? "yes" : "no", g_keepCertificate ? "yes" : "no", g_trimDump ? "yes" : "no", g_calcCrc ? "yes" : "no");
shared_data.data = usbAllocatePageAlignedBuffer(BLOCK_SIZE);
if (!shared_data.data)
{
consolePrint("failed to allocate memory for the dump procedure!\n");
goto end;
}
if ((!g_trimDump && !gamecardGetTotalSize(&gc_size)) || (g_trimDump && !gamecardGetTrimmedSize(&gc_size)) || !gc_size)
{
consolePrint("failed to get gamecard size!\n");
goto end;
}
shared_data.total_size = gc_size;
consolePrint("gamecard size: 0x%lX\n", gc_size);
if (g_appendKeyArea)
{
gc_size += sizeof(GameCardKeyArea);
if (!dumpGameCardSecurityInformation(&gc_security_information)) goto end;
memcpy(&(gc_key_area.initial_data), &(gc_security_information.initial_data), sizeof(GameCardInitialData));
if (g_calcCrc)
{
key_area_crc = crc32Calculate(&gc_key_area, sizeof(GameCardKeyArea));
shared_data.full_xci_crc = key_area_crc;
}
consolePrint("gamecard size (with key area): 0x%lX\n", gc_size);
}
snprintf(path, MAX_ELEMENTS(path), " (%s) (%s) (%s).xci", g_appendKeyArea ? "keyarea" : "keyarealess", g_keepCertificate ? "cert" : "certless", g_trimDump ? "trimmed" : "untrimmed");
filename = generateOutputFileName(path);
if (!filename) goto end;
if (g_useUsbHost)
{
if (!usbSendFilePropertiesCommon(gc_size, filename))
@ -783,7 +783,7 @@ static bool saveGameCardImage(void)
consolePrint("failed to send file properties for \"%s\"!\n", filename);
goto end;
}
if (g_appendKeyArea && !usbSendFileData(&gc_key_area, sizeof(GameCardKeyArea)))
{
consolePrint("failed to send gamecard key area data!\n");
@ -795,49 +795,49 @@ static bool saveGameCardImage(void)
consolePrint("failed to create concatenation file for \"%s\"!\n", filename);
goto end;
}
shared_data.fp = fopen(filename, "wb");
if (!shared_data.fp)
{
consolePrint("failed to open \"%s\" for writing!\n", filename);
goto end;
}
if (g_appendKeyArea && fwrite(&gc_key_area, 1, sizeof(GameCardKeyArea), shared_data.fp) != sizeof(GameCardKeyArea))
{
consolePrint("failed to write gamecard key area data!\n");
goto end;
}
}
consolePrint("creating threads\n");
utilsCreateThread(&read_thread, read_thread_func, &shared_data, 2);
utilsCreateThread(&write_thread, write_thread_func, &shared_data, 2);
u8 prev_time = 0;
u64 prev_size = 0;
u8 percent = 0;
time_t start = 0, btn_cancel_start_tmr = 0, btn_cancel_end_tmr = 0;
bool btn_cancel_cur_state = false, btn_cancel_prev_state = false;
consolePrint("hold b to cancel\n\n");
start = time(NULL);
while(shared_data.data_written < shared_data.total_size)
{
if (shared_data.read_error || shared_data.write_error) break;
struct tm ts = {0};
time_t now = time(NULL);
localtime_r(&now, &ts);
size_t size = shared_data.data_written;
utilsScanPads();
btn_cancel_cur_state = (utilsGetButtonsHeld() & HidNpadButton_B);
if (btn_cancel_cur_state && btn_cancel_cur_state != btn_cancel_prev_state)
{
btn_cancel_start_tmr = now;
@ -855,43 +855,43 @@ static bool saveGameCardImage(void)
} else {
btn_cancel_start_tmr = btn_cancel_end_tmr = 0;
}
btn_cancel_prev_state = btn_cancel_cur_state;
if (prev_time == ts.tm_sec || prev_size == size) continue;
percent = (u8)((size * 100) / shared_data.total_size);
prev_time = ts.tm_sec;
prev_size = size;
printf("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_data.total_size, percent, (now - start));
consoleUpdate(NULL);
}
start = (time(NULL) - start);
consolePrint("\nwaiting for threads to join\n");
utilsJoinThread(&read_thread);
consolePrint("read_thread done: %lu\n", time(NULL));
utilsJoinThread(&write_thread);
consolePrint("write_thread done: %lu\n", time(NULL));
if (shared_data.read_error || shared_data.write_error)
{
consolePrint("i/o error\n");
goto end;
}
if (shared_data.transfer_cancelled)
{
consolePrint("process cancelled\n");
goto end;
}
printf("process completed in %lu seconds\n", start);
success = true;
if (g_calcCrc)
{
if (g_appendKeyArea) printf("key area crc: %08X | ", key_area_crc);
@ -899,20 +899,20 @@ static bool saveGameCardImage(void)
if (g_appendKeyArea) printf(" | xci crc (with key area): %08X", shared_data.full_xci_crc);
printf("\n");
}
end:
if (shared_data.fp)
{
fclose(shared_data.fp);
shared_data.fp = NULL;
}
if (!success && !g_useUsbHost) utilsRemoveConcatenationFile(filename);
if (shared_data.data) free(shared_data.data);
if (filename) free(filename);
return success;
}
@ -922,29 +922,29 @@ static bool saveConsoleLafwBlob(void)
LotusAsicFirmwareBlob lafw_blob = {0};
bool success = false;
u32 crc = 0;
if (!gamecardGetLotusAsicFirmwareBlob(&lafw_blob, &lafw_version))
{
consolePrint("failed to get console lafw blob\n");
goto end;
}
const char *fw_type_str = gamecardGetLafwTypeString(lafw_blob.fw_type);
if (!fw_type_str) fw_type_str = "Unknown";
const char *dev_type_str = gamecardGetLafwDeviceTypeString(lafw_blob.device_type);
if (!dev_type_str) dev_type_str = "Unknown";
consolePrint("get console lafw blob ok\n");
crc = crc32Calculate(&lafw_blob, sizeof(LotusAsicFirmwareBlob));
snprintf(path, MAX_ELEMENTS(path), "LAFW (%s) (%s) (v%lu) (%08X).bin", fw_type_str, dev_type_str, lafw_version, crc);
if (!saveFileData(path, &lafw_blob, sizeof(LotusAsicFirmwareBlob))) goto end;
printf("successfully saved lafw blob as \"%s\"\n", path);
success = true;
end:
return success;
}
@ -982,25 +982,25 @@ static void read_thread_func(void *arg)
shared_data->read_error = true;
goto end;
}
u8 *buf = malloc(BLOCK_SIZE);
if (!buf)
{
shared_data->read_error = true;
goto end;
}
for(u64 offset = 0, blksize = BLOCK_SIZE; offset < shared_data->total_size; offset += blksize)
{
if (blksize > (shared_data->total_size - offset)) blksize = (shared_data->total_size - offset);
/* Check if the transfer has been cancelled by the user */
if (shared_data->transfer_cancelled)
{
condvarWakeAll(&g_writeCondvar);
break;
}
/* Read current data chunk */
shared_data->read_error = !gamecardReadStorage(buf, blksize, offset);
if (shared_data->read_error)
@ -1008,39 +1008,39 @@ static void read_thread_func(void *arg)
condvarWakeAll(&g_writeCondvar);
break;
}
/* Remove certificate */
if (!g_keepCertificate && offset == 0) memset(buf + GAMECARD_CERTIFICATE_OFFSET, 0xFF, sizeof(FsGameCardCertificate));
/* Update checksum */
if (g_calcCrc)
{
shared_data->xci_crc = crc32CalculateWithSeed(shared_data->xci_crc, buf, blksize);
if (g_appendKeyArea) shared_data->full_xci_crc = crc32CalculateWithSeed(shared_data->full_xci_crc, buf, blksize);
}
/* Wait until the previous data chunk has been written */
mutexLock(&g_fileMutex);
if (shared_data->data_size && !shared_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex);
if (shared_data->write_error)
{
mutexUnlock(&g_fileMutex);
break;
}
/* Copy current file data chunk to the shared buffer */
memcpy(shared_data->data, buf, blksize);
shared_data->data_size = blksize;
/* Wake up the write thread to continue writing data */
mutexUnlock(&g_fileMutex);
condvarWakeAll(&g_writeCondvar);
}
free(buf);
end:
threadExit();
}
@ -1053,21 +1053,21 @@ static void write_thread_func(void *arg)
shared_data->write_error = true;
goto end;
}
while(shared_data->data_written < shared_data->total_size)
{
/* Wait until the current file data chunk has been read */
mutexLock(&g_fileMutex);
if (!shared_data->data_size && !shared_data->read_error) condvarWait(&g_writeCondvar, &g_fileMutex);
if (shared_data->read_error || shared_data->transfer_cancelled)
{
if (shared_data->transfer_cancelled && g_useUsbHost) usbCancelFileTransfer();
mutexUnlock(&g_fileMutex);
break;
}
/* Write current file data chunk */
if (g_useUsbHost)
{
@ -1075,20 +1075,20 @@ static void write_thread_func(void *arg)
} else {
shared_data->write_error = (fwrite(shared_data->data, 1, shared_data->data_size, shared_data->fp) != shared_data->data_size);
}
if (!shared_data->write_error)
{
shared_data->data_written += shared_data->data_size;
shared_data->data_size = 0;
}
/* Wake up the read thread to continue reading data */
mutexUnlock(&g_fileMutex);
condvarWakeAll(&g_readCondvar);
if (shared_data->write_error) break;
}
end:
threadExit();
}

File diff suppressed because it is too large Load diff

View file

@ -66,7 +66,7 @@ static void utilsWaitForButtonPress(u64 flag)
/* Don't consider stick movement as button inputs. */
if (!flag) flag = ~(HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \
HidNpadButton_StickRUp | HidNpadButton_StickRDown);
while(appletMainLoop())
{
utilsScanPads();
@ -91,21 +91,21 @@ static void read_thread_func(void *arg)
shared_data->read_error = true;
goto end;
}
u8 *buf = malloc(BLOCK_SIZE);
if (!buf)
{
shared_data->read_error = true;
goto end;
}
u64 file_table_offset = 0;
u64 file_table_size = shared_data->romfs_ctx->file_table_size;
RomFileSystemFileEntry *file_entry = NULL;
char path[FS_MAX_PATH] = {0};
sprintf(path, "sdmc:/romfs");
while(file_table_offset < file_table_size)
{
/* Check if the transfer has been cancelled by the user. */
@ -114,20 +114,20 @@ static void read_thread_func(void *arg)
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;
/* Close file. */
if (shared_data->fd)
{
fclose(shared_data->fd);
shared_data->fd = NULL;
}
/* Retrieve RomFS file entry information. */
shared_data->read_error = (!(file_entry = romfsGetFileEntryByOffset(shared_data->romfs_ctx, file_table_offset)) || \
!romfsGeneratePathFromFileEntry(shared_data->romfs_ctx, file_entry, path + 11, FS_MAX_PATH - 11, RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly));
@ -136,10 +136,10 @@ static void read_thread_func(void *arg)
condvarWakeAll(&g_writeCondvar);
break;
}
/* Create directory tree. */
utilsCreateDirectoryTree(path, false);
/* Create file. */
shared_data->read_error = ((shared_data->fd = fopen(path, "wb")) == NULL);
if (shared_data->read_error)
@ -147,18 +147,18 @@ static void read_thread_func(void *arg)
condvarWakeAll(&g_writeCondvar);
break;
}
for(u64 offset = 0, blksize = BLOCK_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 = !romfsReadFileEntryData(shared_data->romfs_ctx, file_entry, buf, blksize, offset);
if (shared_data->read_error)
@ -166,47 +166,47 @@ static void read_thread_func(void *arg)
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);
}
/* 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->fd)
{
fclose(shared_data->fd);
shared_data->fd = NULL;
}
if ((shared_data->read_error || shared_data->write_error || shared_data->transfer_cancelled) && *path) remove(path);
free(buf);
end:
threadExit();
}
@ -219,20 +219,20 @@ static void write_thread_func(void *arg)
shared_data->write_error = true;
goto end;
}
while(shared_data->data_written < shared_data->total_size)
{
/* Wait until the current file data chunk has been read */
mutexLock(&g_fileMutex);
if (!shared_data->data_size && !shared_data->read_error) condvarWait(&g_writeCondvar, &g_fileMutex);
if (shared_data->read_error || shared_data->transfer_cancelled || !shared_data->fd)
{
mutexUnlock(&g_fileMutex);
break;
}
/* Write current file data chunk */
shared_data->write_error = (fwrite(shared_data->data, 1, shared_data->data_size, shared_data->fd) != shared_data->data_size);
if (!shared_data->write_error)
@ -240,14 +240,14 @@ static void write_thread_func(void *arg)
shared_data->data_written += shared_data->data_size;
shared_data->data_size = 0;
}
/* Wake up the read thread to continue reading data */
mutexUnlock(&g_fileMutex);
condvarWakeAll(&g_readCondvar);
if (shared_data->write_error) break;
}
end:
threadExit();
}
@ -255,37 +255,37 @@ end:
u8 get_program_id_offset(TitleInfo *info, u32 program_count)
{
if (program_count <= 1) return 0;
u8 id_offset = 0;
u32 selected_idx = 0, page_size = 30, scroll = 0;
char nca_id_str[0x21] = {0};
bool applet_status = true;
NcmContentInfo **content_infos = calloc(program_count, sizeof(NcmContentInfo*));
if (!content_infos) return 0;
for(u32 i = 0, j = 0; i < info->content_count && j < program_count; i++)
{
if (info->content_infos[i].content_type != NcmContentType_Program) continue;
content_infos[j++] = &(info->content_infos[i]);
}
while((applet_status = appletMainLoop()))
{
consoleClear();
printf("select a program nca to dump the romfs from.\n\n");
for(u32 i = scroll; i < program_count; i++)
{
if (i >= (scroll + page_size)) break;
utilsGenerateHexStringFromData(nca_id_str, sizeof(nca_id_str), content_infos[i]->content_id.c, sizeof(content_infos[i]->content_id.c), false);
printf("%s%s.nca (ID offset #%u)\n", i == selected_idx ? " -> " : " ", nca_id_str, content_infos[i]->id_offset);
}
printf("\n");
consoleUpdate(NULL);
u64 btn_down = 0, btn_held = 0;
while((applet_status = appletMainLoop()))
{
@ -294,9 +294,9 @@ u8 get_program_id_offset(TitleInfo *info, u32 program_count)
btn_held = utilsGetButtonsHeld();
if (btn_down || btn_held) break;
}
if (!applet_status) break;
if (btn_down & HidNpadButton_A)
{
id_offset = content_infos[selected_idx]->id_offset;
@ -305,7 +305,7 @@ u8 get_program_id_offset(TitleInfo *info, u32 program_count)
if ((btn_down & HidNpadButton_Down) || (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown)))
{
selected_idx++;
if (selected_idx >= program_count)
{
if (btn_down & HidNpadButton_Down)
@ -323,7 +323,7 @@ u8 get_program_id_offset(TitleInfo *info, u32 program_count)
if ((btn_down & HidNpadButton_Up) || (btn_held & (HidNpadButton_StickLUp | HidNpadButton_StickRUp)))
{
selected_idx--;
if (selected_idx == UINT32_MAX)
{
if (btn_down & HidNpadButton_Up)
@ -339,22 +339,22 @@ u8 get_program_id_offset(TitleInfo *info, u32 program_count)
scroll--;
}
}
if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp)) svcSleepThread(50000000); // 50 ms
}
free(content_infos);
return (applet_status ? id_offset : (u8)program_count);
}
static TitleInfo *get_latest_patch_info(TitleInfo *patch_info)
{
if (!patch_info || patch_info->meta_key.type != NcmContentMetaType_Patch) return NULL;
TitleInfo *output = patch_info, *tmp = patch_info;
u32 highest_version = patch_info->version.value;
while((tmp = tmp->next) != NULL)
{
if (tmp->version.value > highest_version)
@ -363,131 +363,131 @@ static TitleInfo *get_latest_patch_info(TitleInfo *patch_info)
highest_version = output->version.value;
}
}
return output;
}
int main(int argc, char *argv[])
{
int ret = 0;
if (!utilsInitializeResources(argc, (const char**)argv))
{
ret = -1;
goto out;
}
/* Configure input. */
/* Up to 8 different, full controller inputs. */
/* Individual Joy-Cons not supported. */
padConfigureInput(8, HidNpadStyleSet_NpadFullCtrl);
padInitializeWithMask(&g_padState, 0x1000000FFUL);
consoleInit(NULL);
u32 app_count = 0;
TitleApplicationMetadata **app_metadata = NULL;
TitleUserApplicationData user_app_data = {0};
u32 selected_idx = 0, page_size = 30, scroll = 0;
bool applet_status = true, exit_prompt = true;
u8 *buf = NULL;
NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL;
RomFileSystemContext romfs_ctx = {0};
ThreadSharedData shared_data = {0};
Thread read_thread = {0}, write_thread = {0};
app_metadata = titleGetApplicationMetadataEntries(false, &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");
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((applet_status = appletMainLoop()))
{
consoleClear();
printf("select a user application to dump its romfs.\nif an update is available, patch romfs data will be dumped instead.\ndata will be saved to \"sdmc:/romfs\".\npress b to exit.\n\n");
printf("title: %u / %u\n", selected_idx + 1, app_count);
printf("selected title: %016lX - %s\n\n", app_metadata[selected_idx]->title_id, app_metadata[selected_idx]->lang_entry.name);
for(u32 i = scroll; i < app_count; i++)
{
if (i >= (scroll + page_size)) break;
printf("%s%016lX - %s\n", i == selected_idx ? " -> " : " ", app_metadata[i]->title_id, app_metadata[i]->lang_entry.name);
}
printf("\n");
consoleUpdate(NULL);
bool gc_update = false;
u64 btn_down = 0, btn_held = 0;
while((applet_status = appletMainLoop()))
{
utilsScanPads();
btn_down = utilsGetButtonsDown();
btn_held = utilsGetButtonsHeld();
if (btn_down || btn_held) break;
if (titleIsGameCardInfoUpdated())
{
free(app_metadata);
app_metadata = titleGetApplicationMetadataEntries(false, &app_count);
if (!app_metadata)
{
consolePrint("\napp metadata failed\n");
goto out2;
}
selected_idx = scroll = 0;
gc_update = true;
break;
}
}
if (!applet_status) break;
if (gc_update) continue;
if (btn_down & HidNpadButton_A)
{
if (!titleGetUserApplicationData(app_metadata[selected_idx]->title_id, &user_app_data) || !user_app_data.app_info)
@ -497,13 +497,13 @@ int main(int argc, char *argv[])
titleFreeUserApplicationData(&user_app_data);
continue;
}
break;
} else
if ((btn_down & HidNpadButton_Down) || (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown)))
{
selected_idx++;
if (selected_idx >= app_count)
{
if (btn_down & HidNpadButton_Down)
@ -521,7 +521,7 @@ int main(int argc, char *argv[])
if ((btn_down & HidNpadButton_Up) || (btn_held & (HidNpadButton_StickLUp | HidNpadButton_StickRUp)))
{
selected_idx--;
if (selected_idx == UINT32_MAX)
{
if (btn_down & HidNpadButton_Up)
@ -542,60 +542,60 @@ int main(int argc, char *argv[])
exit_prompt = false;
goto out2;
}
if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp)) svcSleepThread(50000000); // 50 ms
}
if (!applet_status)
{
exit_prompt = false;
goto out2;
}
u32 program_count = titleGetContentCountByType(user_app_data.app_info, NcmContentType_Program);
if (!program_count)
{
consolePrint("base app has no program ncas!\n");
goto out2;
}
u8 program_id_offset = get_program_id_offset(user_app_data.app_info, program_count);
if (program_id_offset >= program_count)
{
exit_prompt = false;
goto out2;
}
consoleClear();
consolePrint("selected title:\n%s (%016lX)\n\n", app_metadata[selected_idx]->lang_entry.name, app_metadata[selected_idx]->title_id + program_id_offset);
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, program_id_offset), user_app_data.app_info->version.value, NULL))
{
consolePrint("nca initialize base ctx failed\n");
goto out2;
}
TitleInfo *latest_patch = NULL;
if (user_app_data.patch_info) latest_patch = get_latest_patch_info(user_app_data.patch_info);
if (base_nca_ctx->fs_ctx[1].has_sparse_layer && (!latest_patch || latest_patch->version.value < user_app_data.app_info->version.value))
{
consolePrint("base app is a sparse title and no v%u or greater update could be found\n", user_app_data.app_info->version.value);
goto out2;
}
if (latest_patch)
{
consolePrint("using patch romfs with update v%u\n", latest_patch->version.value);
if (!ncaInitializeContext(update_nca_ctx, latest_patch->storage_id, (latest_patch->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
titleGetContentInfoByTypeAndIdOffset(latest_patch, NcmContentType_Program, program_id_offset), latest_patch->version.value, NULL))
{
consolePrint("nca initialize update ctx failed\n");
goto out2;
}
if (!romfsInitializeContext(&romfs_ctx, &(base_nca_ctx->fs_ctx[1]), &(update_nca_ctx->fs_ctx[1])))
{
consolePrint("romfs initialize ctx failed (update)\n");
@ -603,54 +603,54 @@ int main(int argc, char *argv[])
}
} else {
consolePrint("using base romfs only\n");
if (!romfsInitializeContext(&romfs_ctx, &(base_nca_ctx->fs_ctx[1]), NULL))
{
consolePrint("romfs initialize ctx failed (base)\n");
goto out2;
}
}
shared_data.romfs_ctx = &romfs_ctx;
romfsGetTotalDataSize(&romfs_ctx, &(shared_data.total_size));
consolePrint("romfs initialize ctx succeeded\n");
shared_data.fd = NULL;
shared_data.data = buf;
shared_data.data_size = 0;
shared_data.data_written = 0;
consolePrint("creating threads\n");
utilsCreateThread(&read_thread, read_thread_func, &shared_data, 2);
utilsCreateThread(&write_thread, write_thread_func, &shared_data, 2);
u8 prev_time = 0;
u64 prev_size = 0;
u8 percent = 0;
time_t btn_cancel_start_tmr = 0, btn_cancel_end_tmr = 0;
bool btn_cancel_cur_state = false, btn_cancel_prev_state = false;
utilsSetLongRunningProcessState(true);
consolePrint("hold b to cancel\n\n");
time_t start = time(NULL);
while(shared_data.data_written < shared_data.total_size)
{
if (shared_data.read_error || shared_data.write_error) break;
struct tm ts = {0};
time_t now = time(NULL);
localtime_r(&now, &ts);
size_t size = shared_data.data_written;
utilsScanPads();
btn_cancel_cur_state = (utilsGetButtonsHeld() & HidNpadButton_B);
if (btn_cancel_cur_state && btn_cancel_cur_state != btn_cancel_prev_state)
{
btn_cancel_start_tmr = now;
@ -668,67 +668,67 @@ int main(int argc, char *argv[])
} else {
btn_cancel_start_tmr = btn_cancel_end_tmr = 0;
}
btn_cancel_prev_state = btn_cancel_cur_state;
if (prev_time == ts.tm_sec || prev_size == size) continue;
percent = (u8)((size * 100) / shared_data.total_size);
prev_time = ts.tm_sec;
prev_size = size;
printf("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_data.total_size, percent, (now - start));
consoleUpdate(NULL);
}
start = (time(NULL) - start);
consolePrint("\nwaiting for threads to join\n");
utilsJoinThread(&read_thread);
consolePrint("read_thread done: %lu\n", time(NULL));
utilsJoinThread(&write_thread);
consolePrint("write_thread done: %lu\n", time(NULL));
utilsSetLongRunningProcessState(false);
if (shared_data.read_error || shared_data.write_error)
{
consolePrint("i/o error\n");
goto out2;
}
if (shared_data.transfer_cancelled)
{
consolePrint("process cancelled\n");
goto out2;
}
consolePrint("process completed in %lu seconds\n", start);
out2:
if (exit_prompt)
{
consolePrint("press any button to exit\n");
utilsWaitForButtonPress(0);
}
romfsFreeContext(&romfs_ctx);
if (update_nca_ctx) free(update_nca_ctx);
if (base_nca_ctx) free(base_nca_ctx);
titleFreeUserApplicationData(&user_app_data);
if (buf) free(buf);
if (app_metadata) free(app_metadata);
out:
utilsCloseResources();
consoleExit(NULL);
return ret;
}

View file

@ -56,7 +56,7 @@ static void utilsWaitForButtonPress(u64 flag)
/* Don't consider stick movement as button inputs. */
if (!flag) flag = ~(HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \
HidNpadButton_StickRUp | HidNpadButton_StickRDown);
while(appletMainLoop())
{
utilsScanPads();
@ -76,32 +76,32 @@ static void consolePrint(const char *text, ...)
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;
*path = '\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_idx, ncaGetFsSectionTypeName(nca_fs_ctx));
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))
@ -109,75 +109,75 @@ static void dumpPartitionFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
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);
}
if (*path) utilsCommitSdCardFileSystemChanges();
pfsFreeContext(&pfs_ctx);
}
static void dumpRomFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
{
if (!buf || !info || !nca_fs_ctx) return;
u64 romfs_file_table_offset = 0;
RomFileSystemContext romfs_ctx = {0};
RomFileSystemFileEntry *romfs_file_entry = NULL;
size_t path_len = 0;
*path = '\0';
if (!romfsInitializeContext(&romfs_ctx, nca_fs_ctx, NULL))
{
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_idx, ncaGetFsSectionTypeName(nca_fs_ctx));
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)) || \
@ -186,56 +186,56 @@ static void dumpRomFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
consolePrint("romfs get entry / generate path failed for 0x%lX!\n", romfs_file_table_offset);
goto end;
}
utilsCreateDirectoryTree(path, false);
filefd = fopen(path, "wb");
if (!filefd)
{
consolePrint("failed to create \"%s\"!\n", path);
goto end;
}
consolePrint("dumping \"%s\"...\n", path + path_len);
u64 blksize = BLOCK_SIZE;
for(u64 j = 0; j < romfs_file_entry->size; j += blksize)
{
if (blksize > (romfs_file_entry->size - j)) blksize = (romfs_file_entry->size - j);
if (!romfsReadFileEntryData(&romfs_ctx, romfs_file_entry, buf, blksize, j))
{
consolePrint("failed to read 0x%lX block from offset 0x%lX!\n", blksize, j);
goto end;
}
fwrite(buf, 1, blksize, filefd);
}
fclose(filefd);
filefd = NULL;
romfs_file_table_offset += ALIGN_UP(sizeof(RomFileSystemFileEntry) + romfs_file_entry->name_length, 4);
}
consolePrint("romfs dump complete\n");
end:
if (filefd)
{
fclose(filefd);
remove(path);
}
if (*path) utilsCommitSdCardFileSystemChanges();
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:
@ -254,86 +254,86 @@ static void dumpFsSection(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
int main(int argc, char *argv[])
{
int ret = 0;
if (!utilsInitializeResources(argc, (const char**)argv))
{
ret = -1;
goto out;
}
/* Configure input. */
/* Up to 8 different, full controller inputs. */
/* Individual Joy-Cons not supported. */
padConfigureInput(8, HidNpadStyleSet_NpadFullCtrl);
padInitializeWithMask(&g_padState, 0x1000000FFUL);
consoleInit(NULL);
u32 app_count = 0;
TitleApplicationMetadata **app_metadata = NULL;
TitleInfo *cur_title_info = NULL;
u32 selected_idx = 0, menu = 0, page_size = 30, scroll = 0;
u32 title_idx = 0, title_scroll = 0, nca_idx = 0;
char nca_id_str[0x21] = {0};
NcaContext *nca_ctx = NULL;
bool applet_status = true;
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((applet_status = appletMainLoop()))
{
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 == 0)
{
printf("title: %u / %u\n", selected_idx + 1, app_count);
printf("selected title: %016lX - %s\n\n", app_metadata[selected_idx]->title_id, app_metadata[selected_idx]->lang_entry.name);
}
if (menu >= 1) printf("selected title: %016lX - %s\n\n", app_metadata[title_idx]->title_id, app_metadata[title_idx]->lang_entry.name);
if (menu == 2) printf("selected content: %s (%s)\n\n", nca_id_str, titleGetNcmContentTypeName(cur_title_info->content_infos[nca_idx].content_type));
u32 max_val = (menu == 0 ? app_count : (menu == 1 ? cur_title_info->content_count : NCA_FS_HEADER_COUNT));
for(u32 i = scroll; i < max_val; i++)
{
if (i >= (scroll + 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);
@ -348,11 +348,11 @@ int main(int argc, char *argv[])
printf("fs section #%u (%s)\n", i + 1, ncaGetFsSectionTypeName(&(nca_ctx->fs_ctx[i])));
}
}
printf("\n");
consoleUpdate(NULL);
u64 btn_down = 0, btn_held = 0;
while((applet_status = appletMainLoop()))
{
@ -361,13 +361,13 @@ int main(int argc, char *argv[])
btn_held = utilsGetButtonsHeld();
if (btn_down || btn_held) break;
}
if (!applet_status) break;
if (btn_down & HidNpadButton_A)
{
bool error = false;
if (menu == 0)
{
title_idx = selected_idx;
@ -378,9 +378,9 @@ int main(int argc, char *argv[])
nca_idx = selected_idx;
utilsGenerateHexStringFromData(nca_id_str, sizeof(nca_id_str), cur_title_info->content_infos[nca_idx].content_id.c, sizeof(cur_title_info->content_infos[nca_idx].content_id.c), false);
}
menu++;
if (menu == 1)
{
cur_title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, app_metadata[title_idx]->title_id);
@ -405,7 +405,7 @@ int main(int argc, char *argv[])
dumpFsSection(cur_title_info, &(nca_ctx->fs_ctx[selected_idx]));
utilsSetLongRunningProcessState(false);
}
if (error || menu >= 3)
{
consolePrint("press any button to continue\n");
@ -418,7 +418,7 @@ int main(int argc, char *argv[])
if ((btn_down & HidNpadButton_Down) || (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown)))
{
selected_idx++;
if (selected_idx >= max_val)
{
if (btn_down & HidNpadButton_Down)
@ -436,7 +436,7 @@ int main(int argc, char *argv[])
if ((btn_down & HidNpadButton_Up) || (btn_held & (HidNpadButton_StickLUp | HidNpadButton_StickRUp)))
{
selected_idx--;
if (selected_idx == UINT32_MAX)
{
if (btn_down & HidNpadButton_Up)
@ -455,7 +455,7 @@ int main(int argc, char *argv[])
if (btn_down & HidNpadButton_B)
{
menu--;
if (menu == UINT32_MAX)
{
break;
@ -465,29 +465,29 @@ int main(int argc, char *argv[])
if (menu == 0) titleFreeTitleInfo(&cur_title_info);
}
}
if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp)) svcSleepThread(50000000); // 50 ms
}
if (!applet_status) menu = UINT32_MAX;
out2:
if (menu != UINT32_MAX)
{
consolePrint("press any button to exit\n");
utilsWaitForButtonPress(0);
}
if (nca_ctx) free(nca_ctx);
if (buf) free(buf);
if (app_metadata) free(app_metadata);
out:
utilsCloseResources();
consoleExit(NULL);
return ret;
}

View file

@ -67,7 +67,7 @@ static void utilsWaitForButtonPress(u64 flag)
/* Don't consider stick movement as button inputs. */
if (!flag) flag = ~(HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \
HidNpadButton_StickRUp | HidNpadButton_StickRDown);
while(appletMainLoop())
{
utilsScanPads();
@ -92,19 +92,19 @@ static void read_thread_func(void *arg)
shared_data->read_error = true;
goto end;
}
u8 *buf = malloc(BLOCK_SIZE);
if (!buf)
{
shared_data->read_error = true;
goto end;
}
u64 file_table_offset = 0;
u64 file_table_size = shared_data->romfs_ctx->file_table_size;
RomFileSystemFileEntry *file_entry = NULL;
char path[FS_MAX_PATH] = {0};
while(file_table_offset < file_table_size)
{
/* Check if the transfer has been cancelled by the user */
@ -113,7 +113,7 @@ static void read_thread_func(void *arg)
condvarWakeAll(&g_writeCondvar);
break;
}
/* Retrieve RomFS file entry information */
shared_data->read_error = (!(file_entry = romfsGetFileEntryByOffset(shared_data->romfs_ctx, file_table_offset)) || \
!romfsGeneratePathFromFileEntry(shared_data->romfs_ctx, file_entry, path, FS_MAX_PATH, RomFileSystemPathIllegalCharReplaceType_IllegalFsChars));
@ -122,13 +122,13 @@ static void read_thread_func(void *arg)
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 = !usbSendFilePropertiesCommon(file_entry->size, path);
if (shared_data->read_error)
@ -136,18 +136,18 @@ static void read_thread_func(void *arg)
condvarWakeAll(&g_writeCondvar);
break;
}
for(u64 offset = 0, blksize = BLOCK_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 = !romfsReadFileEntryData(shared_data->romfs_ctx, file_entry, buf, blksize, offset);
if (shared_data->read_error)
@ -155,34 +155,34 @@ static void read_thread_func(void *arg)
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);
end:
threadExit();
}
@ -195,23 +195,23 @@ static void write_thread_func(void *arg)
shared_data->write_error = true;
goto end;
}
while(shared_data->data_written < shared_data->total_size)
{
/* Wait until the current file data chunk has been read */
mutexLock(&g_fileMutex);
if (!shared_data->data_size && !shared_data->read_error) condvarWait(&g_writeCondvar, &g_fileMutex);
if (shared_data->read_error || shared_data->transfer_cancelled)
{
if (shared_data->transfer_cancelled) usbCancelFileTransfer();
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)
@ -219,14 +219,14 @@ static void write_thread_func(void *arg)
shared_data->data_written += shared_data->data_size;
shared_data->data_size = 0;
}
/* Wake up the read thread to continue reading data */
mutexUnlock(&g_fileMutex);
condvarWakeAll(&g_readCondvar);
if (shared_data->write_error) break;
}
end:
threadExit();
}
@ -234,37 +234,37 @@ end:
u8 get_program_id_offset(TitleInfo *info, u32 program_count)
{
if (program_count <= 1) return 0;
u8 id_offset = 0;
u32 selected_idx = 0, page_size = 30, scroll = 0;
char nca_id_str[0x21] = {0};
bool applet_status = true;
NcmContentInfo **content_infos = calloc(program_count, sizeof(NcmContentInfo*));
if (!content_infos) return 0;
for(u32 i = 0, j = 0; i < info->content_count && j < program_count; i++)
{
if (info->content_infos[i].content_type != NcmContentType_Program) continue;
content_infos[j++] = &(info->content_infos[i]);
}
while((applet_status = appletMainLoop()))
{
consoleClear();
printf("select a program nca to dump the romfs from.\n\n");
for(u32 i = scroll; i < program_count; i++)
{
if (i >= (scroll + page_size)) break;
utilsGenerateHexStringFromData(nca_id_str, sizeof(nca_id_str), content_infos[i]->content_id.c, sizeof(content_infos[i]->content_id.c), false);
printf("%s%s.nca (ID offset #%u)\n", i == selected_idx ? " -> " : " ", nca_id_str, content_infos[i]->id_offset);
}
printf("\n");
consoleUpdate(NULL);
u64 btn_down = 0, btn_held = 0;
while((applet_status = appletMainLoop()))
{
@ -273,9 +273,9 @@ u8 get_program_id_offset(TitleInfo *info, u32 program_count)
btn_held = utilsGetButtonsHeld();
if (btn_down || btn_held) break;
}
if (!applet_status) break;
if (btn_down & HidNpadButton_A)
{
id_offset = content_infos[selected_idx]->id_offset;
@ -284,7 +284,7 @@ u8 get_program_id_offset(TitleInfo *info, u32 program_count)
if ((btn_down & HidNpadButton_Down) || (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown)))
{
selected_idx++;
if (selected_idx >= program_count)
{
if (btn_down & HidNpadButton_Down)
@ -302,7 +302,7 @@ u8 get_program_id_offset(TitleInfo *info, u32 program_count)
if ((btn_down & HidNpadButton_Up) || (btn_held & (HidNpadButton_StickLUp | HidNpadButton_StickRUp)))
{
selected_idx--;
if (selected_idx == UINT32_MAX)
{
if (btn_down & HidNpadButton_Up)
@ -318,22 +318,22 @@ u8 get_program_id_offset(TitleInfo *info, u32 program_count)
scroll--;
}
}
if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp)) svcSleepThread(50000000); // 50 ms
}
free(content_infos);
return (applet_status ? id_offset : (u8)program_count);
}
static TitleInfo *get_latest_patch_info(TitleInfo *patch_info)
{
if (!patch_info || patch_info->meta_key.type != NcmContentMetaType_Patch) return NULL;
TitleInfo *output = patch_info, *tmp = patch_info;
u32 highest_version = patch_info->version.value;
while((tmp = tmp->next) != NULL)
{
if (tmp->version.value > highest_version)
@ -342,99 +342,99 @@ static TitleInfo *get_latest_patch_info(TitleInfo *patch_info)
highest_version = output->version.value;
}
}
return output;
}
int main(int argc, char *argv[])
{
int ret = 0;
if (!utilsInitializeResources(argc, (const char**)argv))
{
ret = -1;
goto out;
}
/* Configure input. */
/* Up to 8 different, full controller inputs. */
/* Individual Joy-Cons not supported. */
padConfigureInput(8, HidNpadStyleSet_NpadFullCtrl);
padInitializeWithMask(&g_padState, 0x1000000FFUL);
consoleInit(NULL);
u32 app_count = 0;
TitleApplicationMetadata **app_metadata = NULL;
TitleUserApplicationData user_app_data = {0};
u32 selected_idx = 0, page_size = 30, scroll = 0;
bool applet_status = true, exit_prompt = true;
u8 *buf = NULL;
NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL;
RomFileSystemContext romfs_ctx = {0};
ThreadSharedData shared_data = {0};
Thread read_thread = {0}, write_thread = {0};
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(BLOCK_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((applet_status = appletMainLoop()))
{
consoleClear();
printf("select a user application to dump its romfs.\nif an update is available, patch romfs data will be dumped instead.\ndata will be transferred via usb.\npress b to exit.\n\n");
printf("title: %u / %u\n", selected_idx + 1, app_count);
printf("selected title: %016lX - %s\n\n", app_metadata[selected_idx]->title_id, app_metadata[selected_idx]->lang_entry.name);
for(u32 i = scroll; i < app_count; i++)
{
if (i >= (scroll + page_size)) break;
printf("%s%016lX - %s\n", i == selected_idx ? " -> " : " ", app_metadata[i]->title_id, app_metadata[i]->lang_entry.name);
}
printf("\n");
consoleUpdate(NULL);
u64 btn_down = 0, btn_held = 0;
while((applet_status = appletMainLoop()))
{
@ -442,25 +442,25 @@ int main(int argc, char *argv[])
btn_down = utilsGetButtonsDown();
btn_held = utilsGetButtonsHeld();
if (btn_down || btn_held) break;
if (titleIsGameCardInfoUpdated())
{
free(app_metadata);
app_metadata = titleGetApplicationMetadataEntries(false, &app_count);
if (!app_metadata)
{
consolePrint("\napp metadata failed\n");
goto out2;
}
selected_idx = scroll = 0;
break;
}
}
if (!applet_status) break;
if (btn_down & HidNpadButton_A)
{
if (!titleGetUserApplicationData(app_metadata[selected_idx]->title_id, &user_app_data) || !user_app_data.app_info)
@ -470,13 +470,13 @@ int main(int argc, char *argv[])
titleFreeUserApplicationData(&user_app_data);
continue;
}
break;
} else
if ((btn_down & HidNpadButton_Down) || (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown)))
{
selected_idx++;
if (selected_idx >= app_count)
{
if (btn_down & HidNpadButton_Down)
@ -494,7 +494,7 @@ int main(int argc, char *argv[])
if ((btn_down & HidNpadButton_Up) || (btn_held & (HidNpadButton_StickLUp | HidNpadButton_StickRUp)))
{
selected_idx--;
if (selected_idx == UINT32_MAX)
{
if (btn_down & HidNpadButton_Up)
@ -515,60 +515,60 @@ int main(int argc, char *argv[])
exit_prompt = false;
goto out2;
}
if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp)) svcSleepThread(50000000); // 50 ms
}
if (!applet_status)
{
exit_prompt = false;
goto out2;
}
u32 program_count = titleGetContentCountByType(user_app_data.app_info, NcmContentType_Program);
if (!program_count)
{
consolePrint("base app has no program ncas!\n");
goto out2;
}
u8 program_id_offset = get_program_id_offset(user_app_data.app_info, program_count);
if (program_id_offset >= program_count)
{
exit_prompt = false;
goto out2;
}
consoleClear();
consolePrint("selected title:\n%s (%016lX)\n\n", app_metadata[selected_idx]->lang_entry.name, app_metadata[selected_idx]->title_id + program_id_offset);
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, program_id_offset), user_app_data.app_info->version.value, NULL))
{
consolePrint("nca initialize base ctx failed\n");
goto out2;
}
TitleInfo *latest_patch = NULL;
if (user_app_data.patch_info) latest_patch = get_latest_patch_info(user_app_data.patch_info);
if (base_nca_ctx->fs_ctx[1].has_sparse_layer && (!latest_patch || latest_patch->version.value < user_app_data.app_info->version.value))
{
consolePrint("base app is a sparse title and no v%u or greater update could be found\n", user_app_data.app_info->version.value);
goto out2;
}
if (latest_patch)
{
consolePrint("using patch romfs with update v%u\n", latest_patch->version.value);
if (!ncaInitializeContext(update_nca_ctx, latest_patch->storage_id, (latest_patch->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
titleGetContentInfoByTypeAndIdOffset(latest_patch, NcmContentType_Program, program_id_offset), latest_patch->version.value, NULL))
{
consolePrint("nca initialize update ctx failed\n");
goto out2;
}
if (!romfsInitializeContext(&romfs_ctx, &(base_nca_ctx->fs_ctx[1]), &(update_nca_ctx->fs_ctx[1])))
{
consolePrint("romfs initialize ctx failed (update)\n");
@ -576,82 +576,82 @@ int main(int argc, char *argv[])
}
} else {
consolePrint("using base romfs only\n");
if (!romfsInitializeContext(&romfs_ctx, &(base_nca_ctx->fs_ctx[1]), NULL))
{
consolePrint("romfs initialize ctx failed (base)\n");
goto out2;
}
}
shared_data.romfs_ctx = &romfs_ctx;
romfsGetTotalDataSize(&romfs_ctx, &(shared_data.total_size));
consolePrint("romfs initialize ctx succeeded\n");
shared_data.data = buf;
shared_data.data_size = 0;
shared_data.data_written = 0;
consolePrint("waiting for usb connection... ");
time_t start = time(NULL);
u8 usb_host_speed = UsbHostSpeed_None;
while((applet_status = appletMainLoop()))
{
time_t now = time(NULL);
if ((now - start) >= 10) break;
consolePrint("%lu ", now - start);
if ((usb_host_speed = usbIsReady())) break;
utilsSleep(1);
}
if (!applet_status)
{
exit_prompt = false;
goto out2;
}
consolePrint("\n");
if (!usb_host_speed)
{
consolePrint("usb connection failed\n");
goto out2;
}
consolePrint("creating threads\n");
utilsCreateThread(&read_thread, read_thread_func, &shared_data, 2);
utilsCreateThread(&write_thread, write_thread_func, &shared_data, 2);
u8 prev_time = 0;
u64 prev_size = 0;
u8 percent = 0;
time_t btn_cancel_start_tmr = 0, btn_cancel_end_tmr = 0;
bool btn_cancel_cur_state = false, btn_cancel_prev_state = false;
utilsSetLongRunningProcessState(true);
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;
struct tm ts = {0};
time_t now = time(NULL);
localtime_r(&now, &ts);
size_t size = shared_data.data_written;
utilsScanPads();
btn_cancel_cur_state = (utilsGetButtonsHeld() & HidNpadButton_B);
if (btn_cancel_cur_state && btn_cancel_cur_state != btn_cancel_prev_state)
{
btn_cancel_start_tmr = now;
@ -669,67 +669,67 @@ int main(int argc, char *argv[])
} else {
btn_cancel_start_tmr = btn_cancel_end_tmr = 0;
}
btn_cancel_prev_state = btn_cancel_cur_state;
if (prev_time == ts.tm_sec || prev_size == size) continue;
percent = (u8)((size * 100) / shared_data.total_size);
prev_time = ts.tm_sec;
prev_size = size;
printf("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_data.total_size, percent, (now - start));
consoleUpdate(NULL);
}
start = (time(NULL) - start);
consolePrint("\nwaiting for threads to join\n");
utilsJoinThread(&read_thread);
consolePrint("read_thread done: %lu\n", time(NULL));
utilsJoinThread(&write_thread);
consolePrint("write_thread done: %lu\n", time(NULL));
utilsSetLongRunningProcessState(false);
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:
if (exit_prompt)
{
consolePrint("press any button to exit\n");
utilsWaitForButtonPress(0);
}
romfsFreeContext(&romfs_ctx);
if (update_nca_ctx) free(update_nca_ctx);
if (base_nca_ctx) free(base_nca_ctx);
titleFreeUserApplicationData(&user_app_data);
if (buf) free(buf);
if (app_metadata) free(app_metadata);
out:
utilsCloseResources();
consoleExit(NULL);
return ret;
}

View file

@ -51,7 +51,7 @@ static void utilsWaitForButtonPress(u64 flag)
/* Don't consider stick movement as button inputs. */
if (!flag) flag = ~(HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \
HidNpadButton_StickRUp | HidNpadButton_StickRDown);
while(appletMainLoop())
{
utilsScanPads();
@ -82,73 +82,73 @@ static void writeFile(void *buf, size_t buf_size, const char *path)
int main(int argc, char *argv[])
{
int ret = 0;
if (!utilsInitializeResources(argc, (const char**)argv))
{
ret = -1;
goto out;
}
/* Configure input. */
/* Up to 8 different, full controller inputs. */
/* Individual Joy-Cons not supported. */
padConfigureInput(8, HidNpadStyleSet_NpadFullCtrl);
padInitializeWithMask(&g_padState, 0x1000000FFUL);
consoleInit(NULL);
u32 app_count = 0;
TitleApplicationMetadata **app_metadata = NULL;
TitleUserApplicationData user_app_data = {0};
u32 selected_idx = 0, page_size = 30, scroll = 0;
bool applet_status = true, exit_prompt = true;
NcaContext *nca_ctx = NULL;
Ticket tik = {0};
u32 meta_idx = 0;
ContentMetaContext cnmt_ctx = {0};
u32 program_count = 0, program_idx = 0;
ProgramInfoContext *program_info_ctx = NULL;
u32 control_count = 0, control_idx = 0;
NacpContext *nacp_ctx = NULL;
u32 legal_info_count = 0, legal_info_idx = 0;
LegalInfoContext *legal_info_ctx = NULL;
char path[FS_MAX_PATH] = {0};
app_metadata = titleGetApplicationMetadataEntries(false, &app_count);
if (!app_metadata || !app_count)
{
consolePrint("app metadata failed\n");
goto out2;
}
consolePrint("app metadata succeeded\n");
utilsSleep(1);
while((applet_status = appletMainLoop()))
{
consoleClear();
printf("select a user application to generate xmls for.\npress b to exit.\n\n");
printf("title: %u / %u\n", selected_idx + 1, app_count);
printf("selected title: %016lX - %s\n\n", app_metadata[selected_idx]->title_id, app_metadata[selected_idx]->lang_entry.name);
for(u32 i = scroll; i < app_count; i++)
{
if (i >= (scroll + page_size)) break;
printf("%s%016lX - %s\n", i == selected_idx ? " -> " : " ", app_metadata[i]->title_id, app_metadata[i]->lang_entry.name);
}
printf("\n");
consoleUpdate(NULL);
u64 btn_down = 0, btn_held = 0;
while((applet_status = appletMainLoop()))
{
@ -156,25 +156,25 @@ int main(int argc, char *argv[])
btn_down = utilsGetButtonsDown();
btn_held = utilsGetButtonsHeld();
if (btn_down || btn_held) break;
if (titleIsGameCardInfoUpdated())
{
free(app_metadata);
app_metadata = titleGetApplicationMetadataEntries(false, &app_count);
if (!app_metadata)
{
consolePrint("\napp metadata failed\n");
goto out2;
}
selected_idx = scroll = 0;
break;
}
}
if (!applet_status) break;
if (btn_down & HidNpadButton_A)
{
if (!titleGetUserApplicationData(app_metadata[selected_idx]->title_id, &user_app_data) || !user_app_data.app_info)
@ -184,13 +184,13 @@ int main(int argc, char *argv[])
titleFreeUserApplicationData(&user_app_data);
continue;
}
break;
} else
if ((btn_down & HidNpadButton_Down) || (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown)))
{
selected_idx++;
if (selected_idx >= app_count)
{
if (btn_down & HidNpadButton_Down)
@ -208,7 +208,7 @@ int main(int argc, char *argv[])
if ((btn_down & HidNpadButton_Up) || (btn_held & (HidNpadButton_StickLUp | HidNpadButton_StickRUp)))
{
selected_idx--;
if (selected_idx == UINT32_MAX)
{
if (btn_down & HidNpadButton_Up)
@ -229,68 +229,68 @@ int main(int argc, char *argv[])
exit_prompt = false;
goto out2;
}
if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp)) svcSleepThread(50000000); // 50 ms
}
if (!applet_status)
{
exit_prompt = false;
goto out2;
}
consoleClear();
consolePrint("selected title:\n%s (%016lX)\n\n", app_metadata[selected_idx]->lang_entry.name, app_metadata[selected_idx]->title_id);
nca_ctx = calloc(user_app_data.app_info->content_count, sizeof(NcaContext));
if (!nca_ctx)
{
consolePrint("nca ctx calloc failed\n");
goto out2;
}
consolePrint("nca ctx calloc succeeded\n");
meta_idx = (user_app_data.app_info->content_count - 1);
program_count = titleGetContentCountByType(user_app_data.app_info, NcmContentType_Program);
if (program_count && !(program_info_ctx = calloc(program_count, sizeof(ProgramInfoContext))))
{
consolePrint("program info ctx calloc failed\n");
goto out2;
}
control_count = titleGetContentCountByType(user_app_data.app_info, NcmContentType_Control);
if (control_count && !(nacp_ctx = calloc(control_count, sizeof(NacpContext))))
{
consolePrint("nacp ctx calloc failed\n");
goto out2;
}
legal_info_count = titleGetContentCountByType(user_app_data.app_info, NcmContentType_LegalInformation);
if (legal_info_count && !(legal_info_ctx = calloc(legal_info_count, sizeof(LegalInfoContext))))
{
consolePrint("legal info ctx calloc failed\n");
goto out2;
}
for(u32 i = 0, j = 0; i < user_app_data.app_info->content_count; i++)
{
// set meta nca as the last nca
NcmContentInfo *content_info = &(user_app_data.app_info->content_infos[i]);
if (content_info->content_type == NcmContentType_Meta) continue;
if (!ncaInitializeContext(&(nca_ctx[j]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
content_info, user_app_data.app_info->version.value, &tik))
{
consolePrint("%s #%u initialize nca ctx failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset);
goto out2;
}
consolePrint("%s #%u initialize nca ctx succeeded\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset);
if (nca_ctx[j].fs_ctx[0].has_sparse_layer) continue;
switch(content_info->content_type)
{
case NcmContentType_Program:
@ -299,9 +299,9 @@ int main(int argc, char *argv[])
consolePrint("initialize program info ctx failed (%s)\n", nca_ctx[j].content_id_str);
goto out2;
}
nca_ctx[j].content_type_ctx = &(program_info_ctx[program_idx++]);
break;
case NcmContentType_Control:
if (!nacpInitializeContext(&(nacp_ctx[control_idx]), &(nca_ctx[j])))
@ -309,9 +309,9 @@ int main(int argc, char *argv[])
consolePrint("initialize nacp ctx failed (%s)\n", nca_ctx[j].content_id_str);
goto out2;
}
nca_ctx[j].content_type_ctx = &(nacp_ctx[control_idx++]);
break;
case NcmContentType_LegalInformation:
if (!legalInfoInitializeContext(&(legal_info_ctx[legal_info_idx]), &(nca_ctx[j])))
@ -319,53 +319,53 @@ int main(int argc, char *argv[])
consolePrint("initialize legal info ctx failed (%s)\n", nca_ctx[j].content_id_str);
goto out2;
}
nca_ctx[j].content_type_ctx = &(legal_info_ctx[legal_info_idx++]);
break;
default:
break;
}
j++;
}
if (!ncaInitializeContext(&(nca_ctx[meta_idx]), 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_Meta, 0), user_app_data.app_info->version.value, &tik))
{
consolePrint("Meta nca initialize ctx failed\n");
goto out2;
}
consolePrint("Meta nca initialize ctx succeeded\n");
if (!cnmtInitializeContext(&cnmt_ctx, &(nca_ctx[meta_idx])))
{
consolePrint("cnmt initialize ctx failed\n");
goto out2;
}
consolePrint("cnmt initialize ctx succeeded\n");
sprintf(path, "sdmc:/at_xml/%016lX", app_metadata[selected_idx]->title_id);
utilsCreateDirectoryTree(path, true);
if (cnmtGenerateAuthoringToolXml(&cnmt_ctx, nca_ctx, user_app_data.app_info->content_count))
{
consolePrint("cnmt xml succeeded\n");
sprintf(path, "sdmc:/at_xml/%016lX/%s.cnmt.xml", app_metadata[selected_idx]->title_id, cnmt_ctx.nca_ctx->content_id_str);
writeFile(cnmt_ctx.authoring_tool_xml, cnmt_ctx.authoring_tool_xml_size, path);
} else {
consolePrint("cnmt xml failed\n");
}
for(u32 i = 0; i < user_app_data.app_info->content_count; i++)
{
NcaContext *cur_nca_ctx = &(nca_ctx[i]);
if (!cur_nca_ctx->content_type_ctx || cur_nca_ctx->content_type == NcmContentType_Meta) continue;
switch(cur_nca_ctx->content_type)
{
case NcmContentType_Program:
@ -376,12 +376,12 @@ int main(int argc, char *argv[])
consolePrint("program info xml failed (%s | id offset #%u)\n", cur_nca_ctx->content_id_str, cur_nca_ctx->id_offset);
goto out2;
}
consolePrint("program info xml succeeded (%s | id offset #%u)\n", cur_nca_ctx->content_id_str, cur_nca_ctx->id_offset);
sprintf(path, "sdmc:/at_xml/%016lX/%s.programinfo.xml", app_metadata[selected_idx]->title_id, cur_nca_ctx->content_id_str);
writeFile(cur_program_info_ctx->authoring_tool_xml, cur_program_info_ctx->authoring_tool_xml_size, path);
break;
}
case NcmContentType_Control:
@ -392,77 +392,77 @@ int main(int argc, char *argv[])
consolePrint("nacp xml failed (%s | id offset #%u)\n", cur_nca_ctx->content_id_str, cur_nca_ctx->id_offset);
goto out2;
}
consolePrint("nacp xml succeeded (%s | id offset #%u)\n", cur_nca_ctx->content_id_str, cur_nca_ctx->id_offset);
//sprintf(path, "sdmc:/at_xml/%016lX/%s.nacp", app_metadata[selected_idx]->title_id, cur_nca_ctx->content_id_str);
//writeFile(cur_nacp_ctx->data, sizeof(_NacpStruct), path);
sprintf(path, "sdmc:/at_xml/%016lX/%s.nacp.xml", app_metadata[selected_idx]->title_id, cur_nca_ctx->content_id_str);
writeFile(cur_nacp_ctx->authoring_tool_xml, cur_nacp_ctx->authoring_tool_xml_size, path);
for(u8 j = 0; j < cur_nacp_ctx->icon_count; j++)
{
NacpIconContext *icon_ctx = &(cur_nacp_ctx->icon_ctx[j]);
sprintf(path, "sdmc:/at_xml/%016lX/%s.nx.%s.jpg", app_metadata[selected_idx]->title_id, cur_nca_ctx->content_id_str, nacpGetLanguageString(icon_ctx->language));
writeFile(icon_ctx->icon_data, icon_ctx->icon_size, path);
}
break;
}
case NcmContentType_LegalInformation:
{
LegalInfoContext *cur_legal_info_ctx = (LegalInfoContext*)cur_nca_ctx->content_type_ctx;
sprintf(path, "sdmc:/at_xml/%016lX/%s.legalinfo.xml", app_metadata[selected_idx]->title_id, cur_nca_ctx->content_id_str);
writeFile(cur_legal_info_ctx->authoring_tool_xml, cur_legal_info_ctx->authoring_tool_xml_size, path);
consolePrint("legal info xml succeeded (%s | id offset #%u)\n", cur_nca_ctx->content_id_str, cur_nca_ctx->id_offset);
break;
}
default:
break;
}
}
out2:
if (exit_prompt)
{
consolePrint("press any button to exit\n");
utilsWaitForButtonPress(0);
}
if (legal_info_ctx)
{
for(u32 i = 0; i < legal_info_count; i++) legalInfoFreeContext(&(legal_info_ctx[i]));
free(legal_info_ctx);
}
if (nacp_ctx)
{
for(u32 i = 0; i < control_count; i++) nacpFreeContext(&(nacp_ctx[i]));
free(nacp_ctx);
}
if (program_info_ctx)
{
for(u32 i = 0; i < program_count; i++) programInfoFreeContext(&(program_info_ctx[i]));
free(program_info_ctx);
}
cnmtFreeContext(&cnmt_ctx);
if (nca_ctx) free(nca_ctx);
titleFreeUserApplicationData(&user_app_data);
if (app_metadata) free(app_metadata);
out:
utilsCloseResources();
consoleExit(NULL);
return ret;
}

View file

@ -297,7 +297,7 @@ class LogQueueHandler(logging.Handler):
def __init__(self, log_queue):
super().__init__()
self.log_queue = log_queue
def emit(self, record):
if g_cliMode:
msg = self.format(record)
@ -310,7 +310,7 @@ class LogConsole:
def __init__(self, scrolled_text=None):
self.scrolled_text = scrolled_text
self.frame = (self.scrolled_text.winfo_toplevel() if self.scrolled_text else None)
# Create a logging handler using a queue.
self.log_queue = queue.Queue()
self.queue_handler = LogQueueHandler(self.log_queue)
@ -318,10 +318,10 @@ class LogConsole:
formatter = logging.Formatter('%(message)s')
self.queue_handler.setFormatter(formatter)
g_logger.addHandler(self.queue_handler)
# Start polling messages from the queue.
if self.frame: self.frame.after(100, self.poll_log_queue)
def display(self, record):
if self.scrolled_text:
msg = self.queue_handler.format(record)
@ -329,7 +329,7 @@ class LogConsole:
self.scrolled_text.insert(tk.END, msg + '\n', record.levelname)
self.scrolled_text.configure(state='disabled')
self.scrolled_text.yview(tk.END)
def poll_log_queue(self):
# Check every 100 ms if there is a new message in the queue to display.
while True:
@ -339,13 +339,13 @@ class LogConsole:
break
else:
self.display(record)
if self.frame: self.frame.after(100, self.poll_log_queue)
# Loosely based on tk.py from tqdm.
class ProgressBarWindow:
global g_tlb, g_taskbar
def __init__(self, bar_format=None, tk_parent=None, window_title='', window_resize=False, window_protocol=None):
self.n = 0
self.total = 0
@ -357,88 +357,88 @@ class ProgressBarWindow:
self.start_time = 0
self.elapsed_time = 0
self.hwnd = 0
self.tk_parent = tk_parent
self.tk_window = (tk.Toplevel(self.tk_parent) if self.tk_parent else None)
self.withdrawn = False
self.tk_text_var = None
self.tk_n_var = None
self.tk_pbar = None
self.pbar = None
if self.tk_window:
self.tk_window.withdraw()
self.withdrawn = True
if window_title: self.tk_window.title(window_title)
self.tk_window.resizable(window_resize, window_resize)
if window_protocol: self.tk_window.protocol('WM_DELETE_WINDOW', window_protocol)
pbar_frame = ttk.Frame(self.tk_window, padding=5)
pbar_frame.pack()
self.tk_text_var = tk.StringVar(self.tk_window)
tk_label = ttk.Label(pbar_frame, textvariable=self.tk_text_var, wraplength=600, anchor='center', justify='center')
tk_label.pack()
self.tk_n_var = tk.DoubleVar(self.tk_window, value=0)
self.tk_pbar = ttk.Progressbar(pbar_frame, variable=self.tk_n_var, length=450)
self.tk_pbar.configure(maximum=100, mode='indeterminate')
self.tk_pbar.pack()
def __del__(self):
if self.tk_parent: self.tk_parent.after(0, self.tk_window.destroy)
def start(self, total, n=0, divider=1, prefix='', unit='B'):
if (total <= 0) or (n < 0) or (divider < 1): raise Exception('Invalid arguments!')
self.n = n
self.total = total
self.divider = float(divider)
self.total_div = (float(self.total) / self.divider)
self.prefix = prefix
self.unit = unit
if self.tk_pbar:
self.tk_pbar.configure(maximum=self.total_div, mode='determinate')
self.start_time = time.time()
else:
n_div = (float(self.n) / self.divider)
self.pbar = tqdm(initial=n_div, total=self.total_div, unit=self.unit, dynamic_ncols=True, desc=self.prefix, bar_format=self.bar_format)
def update(self, n):
cur_n = (self.n + n)
if cur_n > self.total: return
if self.tk_window:
cur_n_div = (float(cur_n) / self.divider)
self.elapsed_time = (time.time() - self.start_time)
msg = tqdm.format_meter(n=cur_n_div, total=self.total_div, elapsed=self.elapsed_time, prefix=self.prefix, unit=self.unit, bar_format=self.bar_format)
self.tk_text_var.set(msg)
self.tk_n_var.set(cur_n_div)
if self.withdrawn:
self.tk_window.geometry("+{}+{}".format(self.tk_parent.winfo_x(), self.tk_parent.winfo_y()))
self.tk_window.deiconify()
self.tk_window.grab_set()
if g_taskbar:
self.hwnd = int(self.tk_window.wm_frame(), 16)
g_taskbar.ActivateTab(self.hwnd)
g_taskbar.SetProgressState(self.hwnd, g_tlb.TBPF_NORMAL)
self.withdrawn = False
if g_taskbar: g_taskbar.SetProgressValue(self.hwnd, cur_n, self.total)
else:
n_div = (float(n) / self.divider)
self.pbar.update(n_div)
self.n = cur_n
def end(self):
self.n = 0
self.total = 0
@ -448,34 +448,34 @@ class ProgressBarWindow:
self.unit = 'B'
self.start_time = 0
self.elapsed_time = 0
if self.tk_window:
if g_taskbar:
g_taskbar.SetProgressState(self.hwnd, g_tlb.TBPF_NOPROGRESS)
g_taskbar.UnregisterTab(self.hwnd)
self.tk_window.grab_release()
self.tk_window.withdraw()
self.withdrawn = True
self.tk_pbar.configure(maximum=100, mode='indeterminate')
else:
self.pbar.close()
self.pbar = None
print()
def set_prefix(self, prefix):
self.prefix = prefix
def utilsGetPath(path_arg, fallback_path, is_file, create=False):
path = os.path.abspath(os.path.expanduser(os.path.expandvars(path_arg if path_arg else fallback_path)))
if not is_file and create: os.makedirs(path, exist_ok=True)
if not os.path.exists(path) or (is_file and os.path.isdir(path)) or (not is_file and os.path.isfile(path)):
raise Exception("Error: '%s' points to an invalid file/directory." % (path))
return path
def utilsIsValueAlignedToEndpointPacketSize(value):
@ -483,7 +483,7 @@ def utilsIsValueAlignedToEndpointPacketSize(value):
def utilsResetNspInfo():
global g_nspTransferMode, g_nspSize, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath
# Reset NSP transfer mode info.
g_nspTransferMode = False
g_nspSize = 0
@ -495,102 +495,102 @@ def utilsResetNspInfo():
def utilsGetSizeUnitAndDivisor(size):
size_suffixes = [ 'B', 'KiB', 'MiB', 'GiB' ]
size_suffixes_count = len(size_suffixes)
float_size = float(size)
ret = None
for i in range(size_suffixes_count):
if (float_size < pow(1024, i + 1)) or ((i + 1) >= size_suffixes_count):
ret = (size_suffixes[i], pow(1024, i))
break
return ret
def usbGetDeviceEndpoints():
global g_usbEpIn, g_usbEpOut, g_usbEpMaxPacketSize
prev_dev = cur_dev = None
usb_ep_in_lambda = lambda ep: usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN
usb_ep_out_lambda = lambda ep: usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_OUT
usb_version = 0
if g_cliMode: g_logger.info('Please connect a Nintendo Switch console running nxdumptool.')
while True:
# Check if the user decided to stop the server.
if not g_cliMode and g_stopEvent.is_set():
g_stopEvent.clear()
return False
# Find a connected USB device with a matching VID/PID pair.
cur_dev = usb.core.find(idVendor=USB_DEV_VID, idProduct=USB_DEV_PID)
if (cur_dev is None) or ((prev_dev is not None) and (cur_dev.bus == prev_dev.bus) and (cur_dev.address == prev_dev.address)): # Using == here would also compare the backend.
time.sleep(0.1)
continue
# Update previous device.
prev_dev = cur_dev
# Check if the product and manufacturer strings match the ones used by nxdumptool.
#if (cur_dev.manufacturer != USB_DEV_MANUFACTURER) or (cur_dev.product != USB_DEV_PRODUCT):
if cur_dev.manufacturer != USB_DEV_MANUFACTURER:
g_logger.error('Invalid manufacturer/product strings! (bus %u, address %u).' % (cur_dev.bus, cur_dev.address))
time.sleep(0.1)
continue
# Reset device.
cur_dev.reset()
# Set default device configuration, then get the active configuration descriptor.
cur_dev.set_configuration()
cfg = cur_dev.get_active_configuration()
# Get default interface descriptor.
intf = cfg[(0,0)]
# Retrieve endpoints.
g_usbEpIn = usb.util.find_descriptor(intf, custom_match=usb_ep_in_lambda)
g_usbEpOut = usb.util.find_descriptor(intf, custom_match=usb_ep_out_lambda)
if (g_usbEpIn is None) or (g_usbEpOut is None):
g_logger.error('Invalid endpoint addresses! (bus %u, address %u).' % (cur_dev.bus, cur_dev.address))
time.sleep(0.1)
continue
# Save endpoint max packet size and USB version.
g_usbEpMaxPacketSize = g_usbEpIn.wMaxPacketSize
usb_version = cur_dev.bcdUSB
break
g_logger.debug('Successfully retrieved USB endpoints! (bus %u, address %u).' % (cur_dev.bus, cur_dev.address))
g_logger.debug('Max packet size: 0x%X (USB %u.%u).\n' % (g_usbEpMaxPacketSize, usb_version >> 8, (usb_version & 0xFF) >> 4))
if g_cliMode: g_logger.info('Exit nxdumptool or disconnect your console at any time to close this script.')
return True
def usbRead(size, timeout=-1):
rd = None
try:
# Convert read data to a bytes object for easier handling.
rd = bytes(g_usbEpIn.read(size, timeout))
except usb.core.USBError:
if not g_cliMode: traceback.print_exc()
g_logger.error('\nUSB timeout triggered or console disconnected.')
return rd
def usbWrite(data, timeout=-1):
wr = 0
try:
wr = g_usbEpOut.write(data, timeout)
except usb.core.USBError:
if not g_cliMode: traceback.print_exc()
g_logger.error('\nUSB timeout triggered or console disconnected.')
return wr
def usbSendStatus(code):
@ -599,57 +599,57 @@ def usbSendStatus(code):
def usbHandleStartSession(cmd_block):
global g_nxdtVersionMajor, g_nxdtVersionMinor, g_nxdtVersionMicro, g_nxdtAbiVersion, g_nxdtGitCommit
if g_cliMode: print()
g_logger.debug('Received StartSession (%02X) command.' % (USB_CMD_START_SESSION))
# Parse command block.
(g_nxdtVersionMajor, g_nxdtVersionMinor, g_nxdtVersionMicro, g_nxdtAbiVersion, g_nxdtGitCommit) = struct.unpack_from('<BBBB8s', cmd_block, 0)
g_nxdtGitCommit = g_nxdtGitCommit.decode('utf-8').strip('\x00')
# Print client info.
g_logger.info('Client info: nxdumptool v%u.%u.%u, ABI v%u (commit %s).\n' % (g_nxdtVersionMajor, g_nxdtVersionMinor, g_nxdtVersionMicro, g_nxdtAbiVersion, g_nxdtGitCommit))
# Check if we support this ABI version.
if g_nxdtAbiVersion != USB_ABI_VERSION:
g_logger.error('Unsupported ABI version!')
return USB_STATUS_UNSUPPORTED_ABI_VERSION
# Return status code
return USB_STATUS_SUCCESS
def usbHandleSendFileProperties(cmd_block):
global g_nspTransferMode, g_nspSize, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath, g_outputDir, g_tkRoot, g_progressBarWindow
if g_cliMode and not g_nspTransferMode: print()
g_logger.debug('Received SendFileProperties (%02X) command.' % (USB_CMD_SEND_FILE_PROPERTIES))
# Parse command block.
(file_size, filename_length, nsp_header_size, raw_filename) = struct.unpack_from('<QII%ds' % (USB_FILE_PROPERTIES_MAX_NAME_LENGTH), cmd_block, 0)
filename = raw_filename.decode('utf-8').strip('\x00')
# Print info.
dbg_str = ('File size: 0x%X | Filename length: 0x%X' % (file_size, filename_length))
if nsp_header_size > 0: dbg_str += (' | NSP header size: 0x%X' % (nsp_header_size))
dbg_str += '.'
g_logger.debug(dbg_str)
file_type_str = ('file' if (not g_nspTransferMode) else 'NSP file entry')
if not g_cliMode or (g_cliMode and not g_nspTransferMode): g_logger.info('Receiving %s: "%s".' % (file_type_str, filename))
# Perform integrity checks
if (not g_nspTransferMode) and file_size and (nsp_header_size >= file_size):
g_logger.error('NSP header size must be smaller than the full NSP size!\n')
return USB_STATUS_MALFORMED_CMD
if g_nspTransferMode and nsp_header_size:
g_logger.error('Received non-zero NSP header size during NSP transfer mode!\n')
return USB_STATUS_MALFORMED_CMD
if (not filename_length) or (filename_length > USB_FILE_PROPERTIES_MAX_NAME_LENGTH):
g_logger.error('Invalid filename length!\n')
return USB_STATUS_MALFORMED_CMD
# Enable NSP transfer mode (if needed).
if (not g_nspTransferMode) and file_size and nsp_header_size:
g_nspTransferMode = True
@ -659,41 +659,41 @@ def usbHandleSendFileProperties(cmd_block):
g_nspFile = None
g_nspFilePath = None
g_logger.debug('NSP transfer mode enabled!\n')
# Perform additional integrity checks and get a file object to work with.
if (not g_nspTransferMode) or (g_nspFile is None):
# Generate full, absolute path to the destination file.
fullpath = os.path.abspath(g_outputDir + os.path.sep + filename)
# Get parent directory path.
dirpath = os.path.dirname(fullpath)
# Create full directory tree.
os.makedirs(dirpath, exist_ok=True)
# Make sure the output filepath doesn't point to an existing directory.
if os.path.exists(fullpath) and (not os.path.isfile(fullpath)):
utilsResetNspInfo()
g_logger.error('Output filepath points to an existing directory! ("%s").\n' % (fullpath))
return USB_STATUS_HOST_IO_ERROR
# Make sure we have enough free space.
(total_space, used_space, free_space) = shutil.disk_usage(dirpath)
if free_space <= file_size:
utilsResetNspInfo()
g_logger.error('Not enough free space available in output volume!\n')
return USB_STATUS_HOST_IO_ERROR
# Get file object.
file = open(fullpath, "wb")
if g_nspTransferMode:
# Update NSP file object.
g_nspFile = file
# Update NSP file path.
g_nspFilePath = fullpath
# Write NSP header padding right away.
file.write(b'\0' * g_nspHeaderSize)
else:
@ -701,24 +701,24 @@ def usbHandleSendFileProperties(cmd_block):
file = g_nspFile
fullpath = g_nspFilePath
dirpath = os.path.dirname(fullpath)
# Check if we're dealing with an empty file or with the first SendFileProperties command from a NSP.
if (not file_size) or (g_nspTransferMode and file_size == g_nspSize):
# Close file (if needed).
if not g_nspTransferMode: file.close()
# Let the command handler take care of sending the status response for us.
return USB_STATUS_SUCCESS
# Send status response before entering the data transfer stage.
usbSendStatus(USB_STATUS_SUCCESS)
# Start data transfer stage.
g_logger.debug('Data transfer started. Saving %s to: "%s".' % (file_type_str, fullpath))
offset = 0
blksize = USB_TRANSFER_BLOCK_SIZE
# Check if we should use the progress bar window.
use_pbar = (((not g_nspTransferMode) and (file_size > USB_TRANSFER_THRESHOLD)) or (g_nspTransferMode and (g_nspSize > USB_TRANSFER_THRESHOLD)))
if use_pbar:
@ -728,10 +728,10 @@ def usbHandleSendFileProperties(cmd_block):
else:
idx = filename.rfind(os.path.sep)
prefix_filename = (filename[idx+1:] if (idx >= 0) else filename)
prefix = ('Current %s: "%s".\n' % (file_type_str, prefix_filename))
prefix += 'Use your console to cancel the file transfer if you wish to do so.'
if (not g_nspTransferMode) or g_nspRemainingSize == (g_nspSize - g_nspHeaderSize):
if not g_nspTransferMode:
# Set current progress to zero and the maximum value to the provided file size.
@ -741,115 +741,115 @@ def usbHandleSendFileProperties(cmd_block):
# Set current progress to the NSP header size and the maximum value to the provided NSP size.
pbar_n = g_nspHeaderSize
pbar_file_size = g_nspSize
# Get progress bar unit and unit divider. These will be used to display and calculate size values using a specific size unit (B, KiB, MiB, GiB).
(unit, unit_divider) = utilsGetSizeUnitAndDivisor(pbar_file_size)
# Display progress bar window.
g_progressBarWindow.start(pbar_file_size, pbar_n, unit_divider, prefix, unit)
else:
# Set current prefix (holds the filename for the current NSP file entry).
g_progressBarWindow.set_prefix(prefix)
def cancelTransfer():
# Cancel file transfer.
file.close()
os.remove(fullpath)
utilsResetNspInfo()
if use_pbar: g_progressBarWindow.end()
# Start transfer process.
start_time = time.time()
while offset < file_size:
# Update block size (if needed).
diff = (file_size - offset)
if blksize > diff: blksize = diff
# Set block size and handle Zero-Length Termination packet (if needed).
rd_size = blksize
if ((offset + blksize) >= file_size) and utilsIsValueAlignedToEndpointPacketSize(blksize): rd_size += 1
# Read current chunk.
chunk = usbRead(rd_size, USB_TRANSFER_TIMEOUT)
if chunk is None:
g_logger.error('Failed to read 0x%X-byte long data chunk!' % (rd_size))
# Cancel file transfer.
cancelTransfer()
# Returning None will make the command handler exit right away.
return None
chunk_size = len(chunk)
# Check if we're dealing with a CancelFileTransfer command.
if chunk_size == USB_CMD_HEADER_SIZE:
(magic, cmd_id, cmd_block_size) = struct.unpack_from('<4sII', chunk, 0)
if (magic == USB_MAGIC_WORD) and (cmd_id == USB_CMD_CANCEL_FILE_TRANSFER):
# Cancel file transfer.
cancelTransfer()
g_logger.debug('Received CancelFileTransfer (%02X) command.' % (USB_CMD_CANCEL_FILE_TRANSFER))
g_logger.warning('Transfer cancelled.')
# Let the command handler take care of sending the status response for us.
return USB_STATUS_SUCCESS
# Write current chunk.
file.write(chunk)
file.flush()
# Update current offset.
offset = (offset + chunk_size)
# Update remaining NSP data size.
if g_nspTransferMode: g_nspRemainingSize -= chunk_size
# Update progress bar window (if needed).
if use_pbar: g_progressBarWindow.update(chunk_size)
elapsed_time = round(time.time() - start_time)
g_logger.debug('File transfer successfully completed in %s!\n' % (tqdm.format_interval(elapsed_time)))
# Close file handle (if needed).
if not g_nspTransferMode: file.close()
# Hide progress bar window (if needed).
if use_pbar and ((not g_nspTransferMode) or (not g_nspRemainingSize)): g_progressBarWindow.end()
return USB_STATUS_SUCCESS
def usbHandleSendNspHeader(cmd_block):
global g_nspTransferMode, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath
nsp_header_size = len(cmd_block)
g_logger.debug('Received SendNspHeader (%02X) command.' % (USB_CMD_SEND_NSP_HEADER))
# Integrity checks.
if not g_nspTransferMode:
g_logger.error('Received NSP header out of NSP transfer mode!\n')
return USB_STATUS_MALFORMED_CMD
if g_nspRemainingSize:
g_logger.error('Received NSP header before receiving all NSP data! (missing 0x%X byte[s]).\n' % (g_nspRemainingSize))
return USB_STATUS_MALFORMED_CMD
if nsp_header_size != g_nspHeaderSize:
g_logger.error('NSP header size mismatch! (0x%X != 0x%X).\n' % (nsp_header_size, g_nspHeaderSize))
return USB_STATUS_MALFORMED_CMD
# Write NSP header.
g_nspFile.seek(0)
g_nspFile.write(cmd_block)
g_nspFile.close()
g_logger.debug('Successfully wrote 0x%X-byte long NSP header to "%s".\n' % (nsp_header_size, g_nspFilePath))
# Disable NSP transfer mode.
utilsResetNspInfo()
return USB_STATUS_SUCCESS
def usbHandleEndSession(cmd_block):
@ -864,32 +864,32 @@ def usbCommandHandler():
USB_CMD_SEND_NSP_HEADER: usbHandleSendNspHeader,
USB_CMD_END_SESSION: usbHandleEndSession
}
# Get device endpoints.
if not usbGetDeviceEndpoints():
if not g_cliMode:
# Update UI.
uiToggleElements(True)
return
if not g_cliMode:
# Update UI.
g_tkCanvas.itemconfigure(g_tkTipMessage, state='normal', text=SERVER_STOP_MSG)
g_tkServerButton.configure(state='disabled')
# Reset NSP info.
utilsResetNspInfo()
while True:
# Read command header.
cmd_header = usbRead(USB_CMD_HEADER_SIZE)
if (cmd_header is None) or (len(cmd_header) != USB_CMD_HEADER_SIZE):
g_logger.error('Failed to read 0x%X-byte long command header!' % (USB_CMD_HEADER_SIZE))
break
# Parse command header.
(magic, cmd_id, cmd_block_size) = struct.unpack_from('<4sII', cmd_header, 0)
# Read command block right away (if needed).
# nxdumptool expects us to read it right after sending the command header.
cmd_block = None
@ -899,25 +899,25 @@ def usbCommandHandler():
rd_size = (cmd_block_size + 1)
else:
rd_size = cmd_block_size
cmd_block = usbRead(rd_size, USB_TRANSFER_TIMEOUT)
if (cmd_block is None) or (len(cmd_block) != cmd_block_size):
g_logger.error('Failed to read 0x%X-byte long command block for command ID %02X!' % (cmd_block_size, cmd_id))
break
# Verify magic word.
if magic != USB_MAGIC_WORD:
g_logger.error('Received command header with invalid magic word!\n')
usbSendStatus(USB_STATUS_INVALID_MAGIC_WORD)
continue
# Get command handler function.
cmd_func = cmd_dict.get(cmd_id, None)
if cmd_func is None:
g_logger.error('Received command header with unsupported ID %02X.\n' % (cmd_id))
usbSendStatus(USB_STATUS_UNSUPPORTED_CMD)
continue
# Verify command block size.
if (cmd_id == USB_CMD_START_SESSION and cmd_block_size != USB_CMD_BLOCK_SIZE_START_SESSION) or \
(cmd_id == USB_CMD_SEND_FILE_PROPERTIES and cmd_block_size != USB_CMD_BLOCK_SIZE_SEND_FILE_PROPERTIES) or \
@ -925,15 +925,15 @@ def usbCommandHandler():
g_logger.error('Invalid command block size for command ID %02X! (0x%X).\n' % (cmd_id, cmd_block_size))
usbSendStatus(USB_STATUS_MALFORMED_CMD)
continue
# Run command handler function.
# Send status response afterwards. Bail out if requested.
status = cmd_func(cmd_block)
if (status is None) or (not usbSendStatus(status)) or (cmd_id == USB_CMD_END_SESSION) or (status == USB_STATUS_UNSUPPORTED_ABI_VERSION):
break
g_logger.info('\nStopping server.')
if not g_cliMode:
# Update UI.
uiToggleElements(True)
@ -944,13 +944,13 @@ def uiStopServer():
def uiStartServer():
global g_outputDir
g_outputDir = g_tkDirText.get('1.0', tk.END).strip()
if not g_outputDir:
# We should never reach this, honestly.
messagebox.showerror('Error', 'You must provide an output directory!', parent=g_tkRoot)
return
# Make sure the full directory tree exists.
try:
os.makedirs(g_outputDir, exist_ok=True)
@ -958,10 +958,10 @@ def uiStartServer():
traceback.print_exc()
messagebox.showerror('Error', 'Unable to create full output directory tree!', parent=g_tkRoot)
return
# Update UI.
uiToggleElements(False)
# Create background server thread.
server_thread = threading.Thread(target=usbCommandHandler, daemon=True)
server_thread.start()
@ -969,17 +969,17 @@ def uiStartServer():
def uiToggleElements(enable):
if enable:
g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocol)
g_tkChooseDirButton.configure(state='normal')
g_tkServerButton.configure(text='Start server', command=uiStartServer, state='normal')
g_tkCanvas.itemconfigure(g_tkTipMessage, state='hidden', text='')
else:
g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocolStub)
g_tkChooseDirButton.configure(state='disabled')
g_tkServerButton.configure(text='Stop server', command=uiStopServer, state='normal')
g_tkCanvas.itemconfigure(g_tkTipMessage, state='normal', text=SERVER_START_MSG)
g_tkScrolledTextLog.configure(state='normal')
g_tkScrolledTextLog.delete('1.0', tk.END)
g_tkScrolledTextLog.configure(state='disabled')
@ -1007,10 +1007,10 @@ def uiInitialize():
global SCALE
global g_tkRoot, g_tkCanvas, g_tkDirText, g_tkChooseDirButton, g_tkServerButton, g_tkTipMessage, g_tkScrolledTextLog
global g_stopEvent, g_tlb, g_taskbar, g_progressBarWindow
# Setup thread event.
g_stopEvent = threading.Event()
# Enable high DPI scaling under Windows (if possible).
dpi_aware = False
if g_isWindowsVista:
@ -1020,80 +1020,80 @@ def uiInitialize():
if not dpi_aware: dpi_aware = (ctypes.windll.shcore.SetProcessDpiAwareness(1) == 0)
except:
traceback.print_exc()
# Enable taskbar features under Windows (if possible).
g_tlb = g_taskbar = None
del_tlb = False
if g_isWindows7:
try:
import comtypes.client as cc
tlb_fp = open(TASKBAR_LIB_PATH, 'wb')
tlb_fp.write(base64.b64decode(TASKBAR_LIB))
tlb_fp.close()
del_tlb = True
g_tlb = cc.GetModule(TASKBAR_LIB_PATH)
g_taskbar = cc.CreateObject('{56FDF344-FD6D-11D0-958A-006097C9A090}', interface=g_tlb.ITaskbarList3)
g_taskbar.HrInit()
except:
traceback.print_exc()
if del_tlb: os.remove(TASKBAR_LIB_PATH)
# Create root Tkinter object.
g_tkRoot = tk.Tk()
g_tkRoot.title(SCRIPT_TITLE)
g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocol)
g_tkRoot.resizable(False, False)
# Set window icon.
try:
icon_image = tk.PhotoImage(data=APP_ICON)
g_tkRoot.wm_iconphoto(True, icon_image)
except:
traceback.print_exc()
# Get screen resolution.
screen_width_px = g_tkRoot.winfo_screenwidth()
screen_height_px = g_tkRoot.winfo_screenheight()
# Get pixel density (DPI).
screen_dpi = round(g_tkRoot.winfo_fpixels('1i'))
# Update scaling factor (if needed).
if g_isWindowsVista and dpi_aware: SCALE = (float(screen_dpi) / WINDOWS_SCALING_FACTOR)
# Determine window size.
window_width_px = uiScaleMeasure(WINDOW_WIDTH)
window_height_px = uiScaleMeasure(WINDOW_HEIGHT)
# Center window.
pos_hor = int((screen_width_px / 2) - (window_width_px / 2))
pos_ver = int((screen_height_px / 2) - (window_height_px / 2))
g_tkRoot.geometry("{}x{}+{}+{}".format(window_width_px, window_height_px, pos_hor, pos_ver))
# Create canvas and fill it with window elements.
g_tkCanvas = tk.Canvas(g_tkRoot, width=window_width_px, height=window_height_px)
g_tkCanvas.pack()
g_tkCanvas.create_text(uiScaleMeasure(60), uiScaleMeasure(30), text='Output directory:', anchor=tk.CENTER)
g_tkDirText = tk.Text(g_tkRoot, height=1, width=45, font=font.nametofont('TkDefaultFont'), wrap='none', state='disabled', bg='#F0F0F0')
uiUpdateDirectoryField(g_outputDir)
g_tkCanvas.create_window(uiScaleMeasure(260), uiScaleMeasure(30), window=g_tkDirText, anchor=tk.CENTER)
g_tkChooseDirButton = tk.Button(g_tkRoot, text='Choose', width=10, command=uiChooseDirectory)
g_tkCanvas.create_window(uiScaleMeasure(450), uiScaleMeasure(30), window=g_tkChooseDirButton, anchor=tk.CENTER)
g_tkServerButton = tk.Button(g_tkRoot, text='Start server', width=15, command=uiStartServer)
g_tkCanvas.create_window(uiScaleMeasure(WINDOW_WIDTH / 2), uiScaleMeasure(70), window=g_tkServerButton, anchor=tk.CENTER)
g_tkTipMessage = g_tkCanvas.create_text(uiScaleMeasure(WINDOW_WIDTH / 2), uiScaleMeasure(100), anchor=tk.CENTER)
g_tkCanvas.itemconfigure(g_tkTipMessage, state='hidden', text='')
g_tkScrolledTextLog = scrolledtext.ScrolledText(g_tkRoot, height=20, width=65, font=font.nametofont('TkDefaultFont'), wrap=tk.WORD, state='disabled')
g_tkScrolledTextLog.tag_config('DEBUG', foreground='gray')
g_tkScrolledTextLog.tag_config('INFO', foreground='black')
@ -1101,57 +1101,57 @@ def uiInitialize():
g_tkScrolledTextLog.tag_config('ERROR', foreground='red')
g_tkScrolledTextLog.tag_config('CRITICAL', foreground='red', underline=1)
g_tkCanvas.create_window(uiScaleMeasure(WINDOW_WIDTH / 2), uiScaleMeasure(280), window=g_tkScrolledTextLog, anchor=tk.CENTER)
g_tkCanvas.create_text(uiScaleMeasure(5), uiScaleMeasure(WINDOW_HEIGHT - 10), text=COPYRIGHT_TEXT, anchor=tk.W)
# Initialize console logger.
console = LogConsole(g_tkScrolledTextLog)
# Initialize progress bar window object.
bar_format = '{desc}\n\n{percentage:.2f}% - {n:.2f} / {total:.2f} {unit}\nElapsed time: {elapsed}. Remaining time: {remaining}.\nSpeed: {rate_fmt}.'
g_progressBarWindow = ProgressBarWindow(bar_format, g_tkRoot, 'File transfer', False, uiHandleExitProtocolStub)
# Enter Tkinter main loop.
g_tkRoot.lift()
g_tkRoot.mainloop()
def cliInitialize():
global g_progressBarWindow
# Initialize console logger.
console = LogConsole()
# Initialize progress bar window object.
bar_format = '{percentage:.2f}% |{bar}| {n:.2f}/{total:.2f} [{elapsed}<{remaining}, {rate_fmt}]'
g_progressBarWindow = ProgressBarWindow(bar_format)
# Print info.
g_logger.info('\n' + SCRIPT_TITLE + '. ' + COPYRIGHT_TEXT + '.')
g_logger.info('Output directory: "' + g_outputDir + '".\n')
# Start USB command handler directly.
usbCommandHandler()
def main():
global g_cliMode, g_outputDir, g_osType, g_osVersion, g_isWindows, g_isWindowsVista, g_isWindows7, g_logger
# Disable warnings.
warnings.filterwarnings("ignore")
# Parse command line arguments.
parser = ArgumentParser(description=SCRIPT_TITLE + '. ' + COPYRIGHT_TEXT + '.')
parser.add_argument('-c', '--cli', required=False, action='store_true', help='Start the script in CLI mode.')
parser.add_argument('-o', '--outdir', required=False, type=str, metavar='DIR', help='Path to output directory. Defaults to "' + DEFAULT_DIR + '".')
args = parser.parse_args()
# Update global flags.
g_cliMode = args.cli
g_outputDir = utilsGetPath(args.outdir, DEFAULT_DIR, False, True)
# Get OS information.
g_osType = platform.system()
g_osVersion = platform.version()
# Get Windows information.
g_isWindows = (g_osType == 'Windows')
g_isWindowsVista = g_isWindows7 = False
@ -1159,10 +1159,10 @@ def main():
win_ver = g_osVersion.split('.')
win_ver_major = int(win_ver[0])
win_ver_minor = int(win_ver[1])
g_isWindowsVista = (win_ver_major >= 6)
g_isWindows7 = (True if (win_ver_major > 6) else (win_ver_major == 6 and win_ver_minor > 0))
# Setup logging mechanism.
logging.basicConfig(level=logging.INFO)
g_logger = logging.getLogger()
@ -1170,7 +1170,7 @@ def main():
# Remove stderr output handler from logger.
log_stderr = g_logger.handlers[0]
g_logger.removeHandler(log_stderr)
if g_cliMode:
# Initialize CLI.
cliInitialize()

View file

@ -38,16 +38,16 @@ namespace nxdt::views
if (center) this->setHorizontalAlign(NVG_ALIGN_CENTER);
}
};
class AboutTab: public brls::List
{
private:
brls::Image *logo = nullptr;
protected:
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) override;
void layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) override;
public:
AboutTab(void);
~AboutTab(void);

View file

@ -45,13 +45,13 @@ namespace nxdt::tasks
TaskIsCancelled, ///< Task has been cancelled.
TaskWaitTimeout ///< Timed out while waiting for the task to finish.
};
eEx e;
AsyncTaskException() = default;
AsyncTaskException(eEx e) : e(e) { }
};
/* Used by AsyncTask to indicate the current status of the asynchronous task. */
enum class AsyncTaskStatus : int
{
@ -59,7 +59,7 @@ namespace nxdt::tasks
RUNNING, ///< The task is currently running.
FINISHED ///< The task is finished.
};
/* Asynchronous task handler class. */
template<typename Progress, typename Result, typename... Params>
class AsyncTask
@ -72,18 +72,18 @@ namespace nxdt::tasks
Progress m_progress{};
bool m_cancelled = false, m_rethrowException = false;
std::exception_ptr m_exceptionPtr{};
/* Runs on the calling thread after doInBackground() finishes execution. */
void finish(Result&& result)
{
std::lock_guard<std::recursive_mutex> lock(this->mtx);
/* Copy result. */
this->m_result = result;
/* Update status. */
this->m_status = AsyncTaskStatus::FINISHED;
/* Call appropiate post-execution function. */
if (this->isCancelled())
{
@ -91,94 +91,94 @@ namespace nxdt::tasks
} else {
this->onPostExecute(this->m_result);
}
/* Rethrow asynchronous task exception (if available). */
if (this->m_rethrowException && this->m_exceptionPtr) std::rethrow_exception(this->m_exceptionPtr);
}
protected:
/* Set class as non-copyable and non-moveable. */
NON_COPYABLE(AsyncTask);
NON_MOVEABLE(AsyncTask);
virtual ~AsyncTask(void) noexcept
{
/* Return right away if the task isn't running. */
if (this->getStatus() != AsyncTaskStatus::RUNNING) return;
/* Cancel task. This won't do anything if it has already been cancelled. */
this->cancel();
/* Return right away if the result was already retrieved. */
if (!this->m_future.valid()) return;
/* Wait until a result is provided by the task thread. */
/* Avoid rethrowing any exceptions here - program execution could end if another exception has already been rethrown. */
m_future.wait();
}
/* Asynchronous task function. */
/* This function should periodically call isCancelled() to determine if it should end prematurely. */
virtual Result doInBackground(const Params&... params) = 0;
/* Posts asynchronous task result. Runs on the asynchronous task thread. */
virtual Result postResult(Result&& result)
{
return result;
}
/* Cleanup function called if the task is cancelled. Runs on the calling thread. */
virtual void onCancelled(const Result& result) { }
/* Post-execution function called right after the task finishes. Runs on the calling thread. */
virtual void onPostExecute(const Result& result) { }
/* Pre-execution function called right before the task starts. Runs on the calling thread. */
virtual void onPreExecute(void) { }
/* Progress update function. Runs on the calling thread. */
virtual void onProgressUpdate(const Progress& progress) { }
/* Stores the current progress inside the class. Runs on the asynchronous task thread. */
virtual void publishProgress(const Progress& progress)
{
std::lock_guard<std::recursive_mutex> lock(this->mtx);
/* Don't proceed if the task isn't running. */
if (this->getStatus() != AsyncTaskStatus::RUNNING || this->isCancelled()) return;
/* Update progress. */
this->m_progress = progress;
}
/* Returns the current progress. May run on both threads. */
Progress getProgress(void)
{
std::lock_guard<std::recursive_mutex> lock(this->mtx);
return this->m_progress;
}
public:
AsyncTask(void) = default;
/* Cancels the task. Runs on the calling thread. */
void cancel(void) noexcept
{
std::lock_guard<std::recursive_mutex> lock(this->mtx);
/* Return right away if the task has already completed, or if it has already been cancelled. */
if (this->getStatus() == AsyncTaskStatus::FINISHED || this->isCancelled()) return;
/* Update cancel flag. */
this->m_cancelled = true;
}
/* Starts the asynchronous task. Runs on the calling thread. */
AsyncTask<Progress, Result, Params...>& execute(const Params&... params)
{
/* Return right away if the task was cancelled before starting. */
if (this->isCancelled()) return *this;
/* Verify task status. */
switch(this->getStatus())
{
@ -189,13 +189,13 @@ namespace nxdt::tasks
default:
break;
}
/* Update task status. */
this->m_status = AsyncTaskStatus::RUNNING;
/* Run onPreExecute() callback. */
this->onPreExecute();
/* Start asynchronous task on a new thread. */
this->m_future = std::async(std::launch::async, [this](const Params&... params) -> Result {
/* Catch any exceptions thrown by the asynchronous task. */
@ -207,44 +207,44 @@ namespace nxdt::tasks
this->m_rethrowException = true;
this->m_exceptionPtr = std::current_exception();
}
return {};
}, params...);
return *this;
}
/* Waits for the asynchronous task to complete, then returns its result. Runs on the calling thread. */
/* If an exception is thrown by the asynchronous task, it will be rethrown by this function. */
Result get(void)
{
auto status = this->getStatus();
/* Throw an exception if the asynchronous task hasn't been executed. */
if (status == AsyncTaskStatus::PENDING) throw AsyncTaskException(AsyncTaskException::eEx::TaskIsPending);
/* If the task is still running, wait until it finishes. */
/* get() calls wait() on its own if the result hasn't been retrieved. */
/* finish() takes care of rethrowing any exceptions thrown by the asynchronous task. */
if (status == AsyncTaskStatus::RUNNING) this->finish(this->m_future.get());
/* Throw an exception if the asynchronous task was cancelled. */
if (this->isCancelled()) throw AsyncTaskException(AsyncTaskException::eEx::TaskIsCancelled);
/* Return result. */
return this->m_result;
}
/* Waits for at most the given time for the asynchronous task to complete, then returns its result. Runs on the calling thread. */
/* If an exception is thrown by the asynchronous task, it will be rethrown by this function. */
template<typename Rep, typename Period>
Result get(const std::chrono::duration<Rep, Period>& timeout)
{
auto status = this->getStatus();
/* Throw an exception if the asynchronous task hasn't been executed. */
if (status == AsyncTaskStatus::PENDING) throw AsyncTaskException(AsyncTaskException::eEx::TaskIsPending);
/* Check if the task is still running. */
if (status == AsyncTaskStatus::RUNNING)
{
@ -259,26 +259,26 @@ namespace nxdt::tasks
/* Retrieve the task result. */
/* finish() takes care of rethrowing any exceptions thrown by the asynchronous task. */
this->finish(this->m_future.get());
/* Throw an exception if the asynchronous task was cancelled. */
if (this->isCancelled()) throw AsyncTaskException(AsyncTaskException::eEx::TaskIsCancelled);
break;
default:
break;
}
}
/* Return result. */
return this->m_result;
}
/* Returns the current task status. Runs on both threads. */
AsyncTaskStatus getStatus(void) noexcept
{
return this->m_status;
}
/* Returns true if the task was cancelled before it completed normally. May be used on both threads. */
/* Can be used by the asynchronous task to return prematurely. */
bool isCancelled(void) noexcept
@ -286,21 +286,21 @@ namespace nxdt::tasks
std::lock_guard<std::recursive_mutex> lock(this->mtx);
return this->m_cancelled;
}
/* Used by the calling thread to refresh the task progress, preferrably inside a loop. Returns true if the task finished. */
/* If an exception is thrown by the asynchronous task, it will be rethrown by this function. */
bool loopCallback(void)
{
std::lock_guard<std::recursive_mutex> lock(this->mtx);
auto status = this->getStatus();
/* Return immediately if the task already finished. */
if (status == AsyncTaskStatus::FINISHED) return true;
/* Return immediately if the task hasn't started, or if its result was already retrieved. */
if (status == AsyncTaskStatus::PENDING || !this->m_future.valid()) return false;
/* Get task thread status without waiting. */
auto thread_status = this->m_future.wait_for(std::chrono::seconds(0));
switch(thread_status)
@ -316,7 +316,7 @@ namespace nxdt::tasks
default:
break;
}
return false;
}
};

View file

@ -38,9 +38,9 @@ size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src,
NX_INLINE void aes128CtrInitializePartialCtr(u8 *out, const u8 *ctr, u64 offset)
{
if (!out || !ctr) return;
offset >>= 4;
for(u8 i = 0; i < 8; i++)
{
out[i] = ctr[0x8 - i - 1];
@ -54,9 +54,9 @@ NX_INLINE void aes128CtrInitializePartialCtr(u8 *out, const u8 *ctr, u64 offset)
NX_INLINE void aes128CtrUpdatePartialCtr(u8 *ctr, u64 offset)
{
if (!ctr) return;
offset >>= 4;
for(u8 i = 0; i < 8; i++)
{
ctr[0x10 - i - 1] = (u8)(offset & 0xFF);
@ -69,15 +69,15 @@ NX_INLINE void aes128CtrUpdatePartialCtr(u8 *ctr, u64 offset)
NX_INLINE void aes128CtrUpdatePartialCtrEx(u8 *ctr, u32 ctr_val, u64 offset)
{
if (!ctr) return;
offset >>= 4;
for(u8 i = 0; i < 8; i++)
{
ctr[0x10 - i - 1] = (u8)(offset & 0xFF);
offset >>= 8;
}
for(u8 i = 0; i < 4; i++)
{
ctr[0x8 - i - 1] = (u8)(ctr_val & 0xFF);

View file

@ -269,7 +269,7 @@ typedef struct {
u8 *extended_data; ///< Pointer to the extended data block within 'raw_data', if available.
u32 extended_data_size; ///< Size of the extended data block within 'raw_data', if available. Kept here for convenience - this is part of the header in 'extended_data'.
u8 *digest; ///< Pointer to the digest within 'raw_data'.
char *authoring_tool_xml; ///< Pointer to a dynamically allocated, NULL-terminated buffer that holds AuthoringTool-like XML data.
char *authoring_tool_xml; ///< Pointer to a dynamically allocated, NULL-terminated buffer that holds AuthoringTool-like XML data.
///< This is always NULL unless cnmtGenerateAuthoringToolXml() is used on this ContentMetaContext.
u64 authoring_tool_xml_size; ///< Size for the AuthoringTool-like XML. This is essentially the same as using strlen() on 'authoring_tool_xml'.
///< This is always 0 unless cnmtGenerateAuthoringToolXml() is used on this ContentMetaContext.

View file

@ -64,7 +64,7 @@
#define SHN_ABS 0xFFF1 /* The symbol has an absolute value that will not change because of relocation. */
#define SHN_COMMON 0xFFF2 /* Labels a common block that has not yet been allocated. */
#define SHN_XINDEX 0xFFFF /* The symbol refers to a specific location within a section, but the section header index is too large to be represented directly. */
#define SHN_HIRESERVE 0xFFFF
#define SHN_HIRESERVE 0xFFFF
/* Macros. */
#define ELF_ST_BIND(x) ((x) >> 4) /* Extracts binding value from a st_info field. */

View file

@ -50,7 +50,7 @@ typedef enum {
NacpUserAccountSwitchLock_Disable = 0,
NacpUserAccountSwitchLock_Enable = 1,
NacpUserAccountSwitchLock_Count = 2, ///< Total values supported by this enum.
// Old.
NacpTouchScreenUsage_None = 0,
NacpTouchScreenUsage_Supported = 1,
@ -89,7 +89,7 @@ typedef enum {
NacpLanguage_SimplifiedChinese = 14,
NacpLanguage_BrazilianPortuguese = 15,
NacpLanguage_Count = 16, ///< Total values supported by this enum.
/// Old.
NacpLanguage_Taiwanese = NacpLanguage_TraditionalChinese,
NacpLanguage_Chinese = NacpLanguage_SimplifiedChinese
@ -113,7 +113,7 @@ typedef enum {
NacpSupportedLanguage_SimplifiedChinese = BIT(14),
NacpSupportedLanguage_BrazilianPortuguese = BIT(15),
NacpSupportedLanguage_Count = 16, ///< Total values supported by this enum. Should always match NacpLanguage_Count.
///< Old.
NacpSupportedLanguage_Taiwanese = NacpSupportedLanguage_TraditionalChinese,
NacpSupportedLanguage_Chinese = NacpSupportedLanguage_SimplifiedChinese
@ -135,7 +135,7 @@ typedef enum {
NacpVideoCapture_Manual = 1,
NacpVideoCapture_Enable = 2,
NacpVideoCapture_Count = 3, ///< Total values supported by this enum.
/// Old.
NacpVideoCapture_Deny = NacpVideoCapture_Disable,
NacpVideoCapture_Allow = NacpVideoCapture_Manual
@ -153,7 +153,7 @@ typedef enum {
NacpPlayLogPolicy_None = 2,
NacpPlayLogPolicy_Closed = 3,
NacpPlayLogPolicy_Count = 4, ///< Total values supported by this enum.
/// Old.
NacpPlayLogPolicy_All = NacpPlayLogPolicy_Open
} NacpPlayLogPolicy;
@ -333,7 +333,7 @@ typedef enum {
NacpContentsAvailabilityTransitionPolicy_Stable = 1,
NacpContentsAvailabilityTransitionPolicy_Changeable = 2,
NacpContentsAvailabilityTransitionPolicy_Count = 3, ///< Total values supported by this enum.
// Old.
NacpContentsAvailabilityTransitionPolicy_Legacy = NacpContentsAvailabilityTransitionPolicy_NoPolicy
} NacpContentsAvailabilityTransitionPolicy;
@ -429,7 +429,7 @@ typedef struct {
u8 data_hash[SHA256_HASH_SIZE]; ///< SHA-256 checksum calculated over the whole NACP. Used to determine if NcaHierarchicalSha256Patch generation is truly needed.
u8 icon_count; ///< NACP icon count. May be zero if no icons are available.
NacpIconContext *icon_ctx; ///< Pointer to a dynamically allocated buffer that holds 'icon_count' NACP icon contexts. May be NULL if no icons are available.
char *authoring_tool_xml; ///< Pointer to a dynamically allocated, NULL-terminated buffer that holds AuthoringTool-like XML data.
char *authoring_tool_xml; ///< Pointer to a dynamically allocated, NULL-terminated buffer that holds AuthoringTool-like XML data.
///< This is always NULL unless nacpGenerateAuthoringToolXml() is used on this NacpContext.
u64 authoring_tool_xml_size; ///< Size for the AuthoringTool-like XML. This is essentially the same as using strlen() on 'authoring_tool_xml'.
///< This is always 0 unless nacpGenerateAuthoringToolXml() is used on this NacpContext.
@ -494,21 +494,21 @@ const char *nacpGetContentsAvailabilityTransitionPolicyString(u8 contents_availa
NX_INLINE void nacpFreeContext(NacpContext *nacp_ctx)
{
if (!nacp_ctx) return;
romfsFreeContext(&(nacp_ctx->romfs_ctx));
romfsFreeFileEntryPatch(&(nacp_ctx->nca_patch));
if (nacp_ctx->data) free(nacp_ctx->data);
if (nacp_ctx->icon_ctx)
{
for(u8 i = 0; i < nacp_ctx->icon_count; i++)
{
if (nacp_ctx->icon_ctx[i].icon_data) free(nacp_ctx->icon_ctx[i].icon_data);
}
free(nacp_ctx->icon_ctx);
}
if (nacp_ctx->authoring_tool_xml) free(nacp_ctx->authoring_tool_xml);
memset(nacp_ctx, 0, sizeof(NacpContext));
}
@ -521,12 +521,12 @@ NX_INLINE bool nacpIsValidIconContext(NacpIconContext *icon_ctx)
NX_INLINE bool nacpIsValidContext(NacpContext *nacp_ctx)
{
if (!nacp_ctx || !nacp_ctx->nca_ctx || !nacp_ctx->romfs_file_entry || !nacp_ctx->data || (!nacp_ctx->icon_count && nacp_ctx->icon_ctx) || (nacp_ctx->icon_count && !nacp_ctx->icon_ctx)) return false;
for(u8 i = 0; i < nacp_ctx->icon_count; i++)
{
if (!nacpIsValidIconContext(&(nacp_ctx->icon_ctx[i]))) return false;
}
return true;
}

View file

@ -372,30 +372,30 @@ typedef struct {
u8 hash_type; ///< NcaHashType.
u8 encryption_type; ///< NcaEncryptionType.
u8 section_type; ///< NcaFsSectionType.
///< PatchInfo-related fields.
bool has_patch_indirect_layer; ///< Set to true if this NCA FS section has an Indirect patch layer.
bool has_patch_aes_ctr_ex_layer; ///< Set to true if this NCA FS section has an AesCtrEx patch layer.
///< SparseInfo-related fields.
bool has_sparse_layer; ///< Set to true if this NCA FS section has a sparse layer.
u64 sparse_table_offset; ///< header.sparse_info.physical_offset + header.sparse_info.bucket.offset. Relative to the start of the NCA content file. Placed here for convenience.
u64 cur_sparse_virtual_offset; ///< Current sparse layer virtual offset. Used for content decryption if a sparse layer is available.
///< CompressionInfo-related fields.
bool has_compression_layer; ///< Set to true if this NCA FS section has a compression layer.
u64 compression_table_offset; ///< hash_target_offset + header.compression_info.bucket.offset. Relative to the start of the FS section. Placed here for convenience.
///< Hash-layer-related fields.
bool skip_hash_layer_crypto; ///< Set to true if hash layer crypto should be skipped while reading section data.
NcaRegion hash_region; ///< Holds the properties for the full hash layer region that precedes the actual FS section data.
///< Crypto-related fields.
u8 ctr[AES_BLOCK_SIZE]; ///< Used internally by NCA functions to update the AES-128-CTR context IV based on the desired offset.
Aes128CtrContext ctr_ctx; ///< Used internally by NCA functions to perform AES-128-CTR crypto.
Aes128XtsContext xts_decrypt_ctx; ///< Used internally by NCA functions to perform AES-128-XTS decryption.
Aes128XtsContext xts_encrypt_ctx; ///< Used internally by NCA functions to perform AES-128-XTS encryption.
///< NSP-related fields.
bool header_written; ///< Set to true after this FS section header has been written to an output dump.
} NcaFsSectionContext;
@ -443,7 +443,7 @@ typedef struct {
///< Otherwise, this holds the unmodified, encrypted NCA header.
NcaDecryptedKeyArea decrypted_key_area;
NcaFsSectionContext fs_ctx[NCA_FS_HEADER_COUNT];
///< NSP-related fields.
bool header_written; ///< Set to true after the NCA header and the FS section headers have been written to an output dump.
void *content_type_ctx; ///< Pointer to a content type context (e.g. ContentMetaContext, ProgramInfoContext, NacpContext, LegalInfoContext). Set to NULL if unused.
@ -566,24 +566,24 @@ NX_INLINE bool ncaVerifyBucketInfo(NcaBucketInfo *bucket)
NX_INLINE void ncaFreeHierarchicalSha256Patch(NcaHierarchicalSha256Patch *patch)
{
if (!patch) return;
for(u32 i = 0; i < NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT; i++)
{
if (patch->hash_region_patch[i].data) free(patch->hash_region_patch[i].data);
}
memset(patch, 0, sizeof(NcaHierarchicalSha256Patch));
}
NX_INLINE void ncaFreeHierarchicalIntegrityPatch(NcaHierarchicalIntegrityPatch *patch)
{
if (!patch) return;
for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++)
{
if (patch->hash_level_patch[i].data) free(patch->hash_level_patch[i].data);
}
memset(patch, 0, sizeof(NcaHierarchicalIntegrityPatch));
}

View file

@ -94,7 +94,7 @@ typedef enum {
NpdmMemoryRegion_Applet = 1,
NpdmMemoryRegion_SecureSystem = 2,
NpdmMemoryRegion_NonSecureSystem = 3,
/// Old.
NpdmMemoryRegion_NonSecure = NpdmMemoryRegion_Application,
NpdmMemoryRegion_Secure = NpdmMemoryRegion_Applet
@ -333,7 +333,7 @@ typedef enum {
NpdmSystemCallId_CreateTransferMemory = BIT(21),
NpdmSystemCallId_CloseHandle = BIT(22),
NpdmSystemCallId_ResetSignal = BIT(23),
///< System calls for index 1.
NpdmSystemCallId_WaitSynchronization = BIT(0),
NpdmSystemCallId_CancelSynchronization = BIT(1),
@ -359,7 +359,7 @@ typedef enum {
NpdmSystemCallId_UnmapPhysicalMemory = BIT(21),
NpdmSystemCallId_GetDebugFutureThreadInfo = BIT(22), ///< Old: SystemCallId_GetFutureThreadInfo.
NpdmSystemCallId_GetLastThreadInfo = BIT(23),
///< System calls for index 2.
NpdmSystemCallId_GetResourceLimitLimitValue = BIT(0),
NpdmSystemCallId_GetResourceLimitCurrentValue = BIT(1),
@ -385,7 +385,7 @@ typedef enum {
NpdmSystemCallId_CreateEvent = BIT(21),
NpdmSystemCallId_Reserved9 = BIT(22),
NpdmSystemCallId_Reserved10 = BIT(23),
///< System calls for index 3.
NpdmSystemCallId_MapPhysicalMemoryUnsafe = BIT(0),
NpdmSystemCallId_UnmapPhysicalMemoryUnsafe = BIT(1),
@ -411,7 +411,7 @@ typedef enum {
NpdmSystemCallId_InvalidateProcessDataCache = BIT(21),
NpdmSystemCallId_StoreProcessDataCache = BIT(22),
NpdmSystemCallId_FlushProcessDataCache = BIT(23),
///< System calls for index 4.
NpdmSystemCallId_DebugActiveProcess = BIT(0),
NpdmSystemCallId_BreakDebugProcess = BIT(1),
@ -437,7 +437,7 @@ typedef enum {
NpdmSystemCallId_UnmapProcessMemory = BIT(21),
NpdmSystemCallId_QueryProcessMemory = BIT(22),
NpdmSystemCallId_MapProcessCodeMemory = BIT(23),
///< System calls for index 5.
NpdmSystemCallId_UnmapProcessCodeMemory = BIT(0),
NpdmSystemCallId_CreateProcess = BIT(1),
@ -447,7 +447,7 @@ typedef enum {
NpdmSystemCallId_CreateResourceLimit = BIT(5),
NpdmSystemCallId_SetResourceLimitLimitValue = BIT(6),
NpdmSystemCallId_CallSecureMonitor = BIT(7),
NpdmSystemCallId_Count = 0xC0 ///< Total values supported by this enum.
} NpdmSystemCallId;

View file

@ -37,7 +37,7 @@ typedef struct {
NpdmContext npdm_ctx; ///< NpdmContext for the NPDM stored in Program NCA ExeFS.
u32 nso_count; ///< Number of NSOs stored in Program NCA FS section #0.
NsoContext *nso_ctx; ///< Pointer to a dynamically allocated buffer that holds 'nso_count' NSO contexts.
char *authoring_tool_xml; ///< Pointer to a dynamically allocated, NULL-terminated buffer that holds AuthoringTool-like XML data.
char *authoring_tool_xml; ///< Pointer to a dynamically allocated, NULL-terminated buffer that holds AuthoringTool-like XML data.
///< This is always NULL unless programInfoGenerateAuthoringToolXml() is used on this ProgramInfoContext.
u64 authoring_tool_xml_size; ///< Size for the AuthoringTool-like XML. This is essentially the same as using strlen() on 'authoring_tool_xml'.
///< This is always 0 unless programInfoGenerateAuthoringToolXml() is used on this ProgramInfoContext.
@ -55,16 +55,16 @@ bool programInfoGenerateAuthoringToolXml(ProgramInfoContext *program_info_ctx);
NX_INLINE void programInfoFreeContext(ProgramInfoContext *program_info_ctx)
{
if (!program_info_ctx) return;
pfsFreeContext(&(program_info_ctx->pfs_ctx));
npdmFreeContext(&(program_info_ctx->npdm_ctx));
if (program_info_ctx->nso_ctx)
{
for(u32 i = 0; i < program_info_ctx->nso_count; i++) nsoFreeContext(&(program_info_ctx->nso_ctx[i]));
free(program_info_ctx->nso_ctx);
}
if (program_info_ctx->authoring_tool_xml) free(program_info_ctx->authoring_tool_xml);
memset(program_info_ctx, 0, sizeof(ProgramInfoContext));
}

View file

@ -202,9 +202,9 @@ NX_INLINE void romfsWriteFileEntryPatchToMemoryBuffer(RomFileSystemContext *ctx,
if (!ctx || ctx->is_patch || !ncaStorageIsValidContext(ctx->default_storage_ctx) || ctx->default_storage_ctx->base_storage_type != NcaStorageBaseStorageType_Regular || !patch || \
(!patch->use_old_format_patch && ctx->default_storage_ctx->nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs) || \
(patch->use_old_format_patch && ctx->default_storage_ctx->nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs)) return;
NcaContext *nca_ctx = (NcaContext*)ctx->default_storage_ctx->nca_fs_ctx->nca_ctx;
if (patch->use_old_format_patch)
{
ncaWriteHierarchicalSha256PatchToMemoryBuffer(nca_ctx, &(patch->old_format_patch), buf, buf_size, buf_offset);

View file

@ -193,16 +193,16 @@ NX_INLINE TikCommonBlock *tikGetCommonBlock(void *buf)
NX_INLINE u64 tikGetTicketSectionRecordsBlockSize(TikCommonBlock *tik_common_block)
{
if (!tik_common_block) return 0;
u64 offset = sizeof(TikCommonBlock), out_size = 0;
for(u32 i = 0; i < tik_common_block->sect_hdr_count; i++)
{
TikESV2SectionRecord *rec = (TikESV2SectionRecord*)((u8*)tik_common_block + offset);
offset += (sizeof(TikESV2SectionRecord) + ((u64)rec->record_count * (u64)rec->record_size));
out_size += offset;
}
return out_size;
}

View file

@ -229,51 +229,51 @@ NX_INLINE bool titleCheckIfDeltaIdBelongsToApplicationId(u64 app_id, u64 delta_i
NX_INLINE u32 titleGetContentCountByType(TitleInfo *info, u8 content_type)
{
if (!info || !info->content_count || !info->content_infos || content_type > NcmContentType_DeltaFragment) return 0;
u32 cnt = 0;
for(u32 i = 0; i < info->content_count; i++)
{
if (info->content_infos[i].content_type == content_type) cnt++;
}
return cnt;
}
NX_INLINE NcmContentInfo *titleGetContentInfoByTypeAndIdOffset(TitleInfo *info, u8 content_type, u8 id_offset)
{
if (!info || !info->content_count || !info->content_infos || content_type > NcmContentType_DeltaFragment) return NULL;
for(u32 i = 0; i < info->content_count; i++)
{
NcmContentInfo *cur_content_info = &(info->content_infos[i]);
if (cur_content_info->content_type == content_type && cur_content_info->id_offset == id_offset) return cur_content_info;
}
return NULL;
}
NX_INLINE u32 titleGetCountFromInfoBlock(TitleInfo *title_info)
{
if (!title_info) return 0;
u32 count = 1;
TitleInfo *cur_info = title_info->previous;
while(cur_info)
{
count++;
cur_info = cur_info->previous;
}
cur_info = title_info->next;
while(cur_info)
{
count++;
cur_info = cur_info->next;
}
return count;
}

View file

@ -37,18 +37,18 @@ namespace nxdt::tasks
size_t size; ///< Total download size.
size_t current; ///< Number of bytes downloaded thus far.
int percentage; ///< Progress percentage.
/// Fields set by DownloadTask::onProgressUpdate().
double speed; ///< Download speed expressed in bytes per second.
std::string eta; ///< Formatted ETA string.
} DownloadTaskProgress;
/* Custom event type used to push download progress updates. */
typedef brls::Event<const DownloadTaskProgress&> DownloadProgressEvent;
/* Used to hold a buffer + size pair with downloaded data. */
typedef std::pair<char*, size_t> DownloadDataResult;
/* Class template to asynchronously download data on a background thread. */
/* Automatically allocates and registers a RepeatingTask on its own, which is started along with the actual task when execute() is called. */
/* This internal RepeatingTask is guaranteed to work on the UI thread, and it is also automatically unregistered on object destruction. */
@ -63,181 +63,181 @@ namespace nxdt::tasks
private:
bool finished = false;
DownloadTask<Result, Params...>* task = nullptr;
protected:
void run(retro_time_t current_time) override final;
public:
DownloadTaskHandler(retro_time_t interval, DownloadTask<Result, Params...>* task);
ALWAYS_INLINE bool isFinished(void)
{
return this->finished;
}
};
private:
DownloadProgressEvent progress_event;
DownloadTaskHandler *task_handler = nullptr;
std::chrono::time_point<std::chrono::steady_clock> start_time{}, prev_time{};
size_t prev_current = 0;
protected:
/* Make the background function overridable. */
virtual Result doInBackground(const Params&... params) override = 0;
/* These functions run on the calling thread. */
void onCancelled(const Result& result) override final;
void onPostExecute(const Result& result) override final;
void onPreExecute(void) override final;
void onProgressUpdate(const DownloadTaskProgress& progress) override final;
public:
DownloadTask(void);
~DownloadTask(void);
/* Runs on the asynchronous task thread. Required by CURL. */
/* Make sure to pass it to either httpDownloadFile() or httpDownloadData() with 'this' as the user pointer. */
static int HttpProgressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
/* Returns the last result from loopCallback(). Runs on the calling thread. */
ALWAYS_INLINE bool isFinished(void)
{
return this->task_handler->isFinished();
}
ALWAYS_INLINE DownloadProgressEvent::Subscription RegisterListener(DownloadProgressEvent::Callback cb)
{
return this->progress_event.subscribe(cb);
}
ALWAYS_INLINE void UnregisterListener(DownloadProgressEvent::Subscription subscription)
{
this->progress_event.unsubscribe(subscription);
}
};
template<typename Result, typename... Params>
DownloadTask<Result, Params...>::DownloadTaskHandler::DownloadTaskHandler(retro_time_t interval, DownloadTask<Result, Params...>* task) : brls::RepeatingTask(interval), task(task)
{
/* Do nothing. */
}
template<typename Result, typename... Params>
void DownloadTask<Result, Params...>::DownloadTaskHandler::run(retro_time_t current_time)
{
brls::RepeatingTask::run(current_time);
if (this->task && !this->finished) this->finished = this->task->loopCallback();
}
template<typename Result, typename... Params>
DownloadTask<Result, Params...>::DownloadTask(void)
{
/* Create task handler. */
this->task_handler = new DownloadTaskHandler(DOWNLOAD_TASK_INTERVAL, this);
}
template<typename Result, typename... Params>
DownloadTask<Result, Params...>::~DownloadTask(void)
{
/* Stop task handler. Borealis' task manager will take care of deleting it. */
this->task_handler->stop();
/* Unregister all event listeners. */
this->progress_event.unsubscribeAll();
}
template<typename Result, typename... Params>
int DownloadTask<Result, Params...>::HttpProgressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
(void)ultotal;
(void)ulnow;
DownloadTaskProgress progress = {0};
DownloadTask<Result, Params...>* task = static_cast<DownloadTask<Result, Params...>*>(clientp);
/* Don't proceed if we're dealing with an invalid task pointer, or if the task has been cancelled. */
if (!task || task->isCancelled()) return 1;
/* Fill struct. */
progress.size = static_cast<size_t>(dltotal);
progress.current = static_cast<size_t>(dlnow);
progress.percentage = (progress.size ? static_cast<int>((progress.current * 100) / progress.size) : 0);
/* Push progress onto the class. */
task->publishProgress(progress);
return 0;
}
template<typename Result, typename... Params>
void DownloadTask<Result, Params...>::onCancelled(const Result& result)
{
(void)result;
/* Pause task handler. */
this->task_handler->pause();
/* Unset long running process state. */
utilsSetLongRunningProcessState(false);
}
template<typename Result, typename... Params>
void DownloadTask<Result, Params...>::onPostExecute(const Result& result)
{
(void)result;
/* Fire task handler immediately to get the last result from loopCallback(), then pause it. */
this->task_handler->fireNow();
this->task_handler->pause();
/* Update progress one last time. */
this->onProgressUpdate(this->getProgress());
/* Unset long running process state. */
utilsSetLongRunningProcessState(false);
}
template<typename Result, typename... Params>
void DownloadTask<Result, Params...>::onPreExecute(void)
{
/* Set long running process state. */
utilsSetLongRunningProcessState(true);
/* Start task handler. */
this->task_handler->start();
/* Set start time. */
this->start_time = this->prev_time = std::chrono::steady_clock::now();
}
template<typename Result, typename... Params>
void DownloadTask<Result, Params...>::onProgressUpdate(const DownloadTaskProgress& progress)
{
AsyncTaskStatus status = this->getStatus();
/* Return immediately if there has been no progress at all, or if it the task has been cancelled. */
bool proceed = (progress.current > prev_current || (progress.current == prev_current && (!progress.size || progress.current >= progress.size)));
if (!proceed || this->isCancelled()) return;
/* Calculate time difference between the last progress update and the current one. */
/* Return immediately if it's less than 1 second, but only if this isn't the last chunk (or if the task is still running, if we don't know the download size). */
std::chrono::time_point<std::chrono::steady_clock> cur_time = std::chrono::steady_clock::now();
std::chrono::duration<double> diff_time = (cur_time - this->prev_time);
double diff_time_conv = diff_time.count();
if (diff_time_conv < 1.0 && ((progress.size && progress.current < progress.size) || status == AsyncTaskStatus::RUNNING)) return;
/* Calculate transferred data size difference between the last progress update and the current one. */
double diff_current = static_cast<double>(progress.current - prev_current);
/* Calculate download speed in bytes per second. */
double speed = (diff_current / diff_time_conv);
/* Fill struct. */
DownloadTaskProgress new_progress = progress;
new_progress.speed = speed;
if (progress.size)
{
/* Calculate remaining data size and ETA if we know the download size. */
@ -248,22 +248,22 @@ namespace nxdt::tasks
/* No download size means no ETA calculation, sadly. */
new_progress.eta = "";
}
/* Set download size if we don't know it and if this is the final chunk. */
if (!new_progress.size && status == AsyncTaskStatus::FINISHED)
{
new_progress.size = new_progress.current;
new_progress.percentage = 100;
}
/* Update class variables. */
this->prev_time = cur_time;
this->prev_current = progress.current;
/* Send updated progress to all listeners. */
this->progress_event.fire(new_progress);
}
/* Asynchronous task to download a file using an output path and a URL. */
class DownloadFileTask: public DownloadTask<bool, std::string, std::string, bool>
{
@ -275,7 +275,7 @@ namespace nxdt::tasks
return httpDownloadFile(path.c_str(), url.c_str(), force_https, DownloadFileTask::HttpProgressCallback, this);
}
};
/* Asynchronous task to store downloaded data into a dynamically allocated buffer using a URL. */
class DownloadDataTask: public DownloadTask<DownloadDataResult, std::string, bool>
{
@ -284,10 +284,10 @@ namespace nxdt::tasks
{
char *buf = NULL;
size_t buf_size = 0;
/* If the process fails or if it's cancelled, httpDownloadData() will take care of freeing up the allocated memory and return NULL. */
buf = httpDownloadData(&buf_size, url.c_str(), force_https, DownloadDataTask::HttpProgressCallback, this);
return std::make_pair(buf, buf_size);
}
};

View file

@ -34,15 +34,15 @@ namespace nxdt::views
{
private:
brls::Label *label = nullptr;
protected:
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) override;
void layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) override;
public:
ErrorFrame(std::string msg = "");
~ErrorFrame(void);
void SetMessage(std::string msg);
};
}

View file

@ -33,18 +33,18 @@ namespace nxdt::views
{
private:
bool highlight, highlight_bg;
protected:
brls::View* getDefaultFocus(void) override
{
return this;
}
bool isHighlightBackgroundEnabled(void) override
{
return this->highlight_bg;
}
void onFocusGained(void) override
{
if (this->highlight)
@ -58,21 +58,21 @@ namespace nxdt::views
if (this->hasParent()) this->getParent()->onChildFocusGained(this);
}
}
public:
template<typename... Types>
FocusableItem(bool highlight, bool highlight_bg, Types... args) : ViewType(args...), highlight(highlight), highlight_bg(highlight_bg) { }
};
/* Define templated classes for the focusable items we're gonna use. */
class FocusableLabel: public FocusableItem<brls::Label>
{
public:
template<typename... Types>
FocusableLabel(Types... args) : FocusableItem<brls::Label>(false, false, args...) { }
};
class FocusableTable: public FocusableItem<brls::Table>
{
public:

View file

@ -33,13 +33,13 @@ namespace nxdt::views
class GameCardTab: public LayeredErrorFrame
{
typedef bool (*GameCardSizeFunc)(u64 *out_size);
private:
RootView *root_view = nullptr;
nxdt::tasks::GameCardStatusEvent::Subscription gc_status_task_sub;
GameCardStatus gc_status = GameCardStatus_NotInserted;
FocusableTable *properties_table = nullptr;
brls::TableRow *capacity = nullptr;
brls::TableRow *total_size = nullptr;
@ -48,16 +48,16 @@ namespace nxdt::views
brls::TableRow *lafw_version = nullptr;
brls::TableRow *sdk_version = nullptr;
brls::TableRow *compatibility_type = nullptr;
brls::ListItem *dump_card_image = nullptr;
brls::ListItem *dump_certificate = nullptr;
brls::ListItem *dump_header = nullptr;
brls::ListItem *dump_decrypted_cardinfo = nullptr;
brls::ListItem *dump_initial_data = nullptr;
brls::ListItem *dump_hfs_partitions = nullptr;
std::string GetFormattedSizeString(GameCardSizeFunc func);
public:
GameCardTab(RootView *root_view);
~GameCardTab(void);

View file

@ -33,21 +33,21 @@ namespace nxdt::views
{
private:
brls::SidebarItem *sidebar_item = nullptr;
protected:
ErrorFrame *error_frame = nullptr;
brls::List *list = nullptr;
bool IsListItemFocused(void);
int GetFocusStackViewIndex(void);
bool UpdateFocusStackViewAtIndex(int index, brls::View *view);
void SwitchLayerView(bool use_error_frame, bool update_focused_view = false, bool update_focus_stack = true);
public:
LayeredErrorFrame(std::string msg = "");
void SetParentSidebarItem(brls::SidebarItem *sidebar_item);
};
}

View file

@ -36,34 +36,34 @@ namespace nxdt::views
private:
brls::ProgressDisplay *progress_display = nullptr;
brls::Label *size_lbl = nullptr, *speed_eta_lbl = nullptr;
std::string GetFormattedSizeString(double size);
protected:
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) override;
void layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) override;
public:
OptionsTabUpdateProgress(void);
~OptionsTabUpdateProgress(void);
void SetProgress(const nxdt::tasks::DownloadTaskProgress& progress);
void willAppear(bool resetState = false) override;
void willDisappear(bool resetState = false) override;
};
/* Update file dialog. */
class OptionsTabUpdateFileDialog: public brls::Dialog
{
private:
nxdt::tasks::DownloadFileTask download_task;
std::string success_str;
public:
OptionsTabUpdateFileDialog(std::string path, std::string url, bool force_https, std::string success_str);
};
/* Update application frame. */
class OptionsTabUpdateApplicationFrame: public brls::StagedAppletFrame
{
@ -72,36 +72,36 @@ namespace nxdt::views
char *json_buf = NULL;
size_t json_buf_size = 0;
UtilsGitHubReleaseJsonData json_data = {0};
brls::Label *wait_lbl = nullptr; /// First stage.
brls::List *changelog_list = nullptr; /// Second stage.
OptionsTabUpdateProgress *update_progress = nullptr; /// Third stage.
nxdt::tasks::DownloadFileTask nro_task;
brls::GenericEvent::Subscription focus_event_sub;
void DisplayChangelog(void);
void DisplayUpdateProgress(void);
protected:
void layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) override;
bool onCancel(void) override;
public:
OptionsTabUpdateApplicationFrame(void);
~OptionsTabUpdateApplicationFrame(void);
};
class OptionsTab: public brls::List
{
private:
RootView *root_view = nullptr;
bool display_notification = true;
brls::menu_timer_t notification_timer = 0.0f;
brls::menu_timer_ctx_entry_t notification_timer_ctx = {0};
void DisplayNotification(std::string str);
public:
OptionsTab(RootView *root_view);

View file

@ -36,45 +36,45 @@ namespace nxdt::views
{
private:
bool applet_mode = false;
brls::Label *applet_mode_lbl = nullptr;
brls::Label *time_lbl = nullptr;
brls::Label *battery_icon = nullptr, *battery_percentage = nullptr;
brls::Label *connection_icon = nullptr, *connection_status_lbl = nullptr;
brls::Label *usb_icon = nullptr, *usb_host_speed_lbl = nullptr;
nxdt::tasks::StatusInfoTask *status_info_task = nullptr;
nxdt::tasks::GameCardTask *gc_status_task = nullptr;
nxdt::tasks::TitleTask *title_task = nullptr;
nxdt::tasks::UmsTask *ums_task = nullptr;
nxdt::tasks::UsbHostTask *usb_host_task = nullptr;
nxdt::tasks::StatusInfoEvent::Subscription status_info_task_sub;
nxdt::tasks::UsbHostEvent::Subscription usb_host_task_sub;
protected:
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) override;
void layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) override;
brls::View *getDefaultFocus(void) override;
public:
RootView(void);
~RootView(void);
static std::string GetFormattedDateString(const struct tm& timeinfo);
/* Wrappers for task functions. */
ALWAYS_INLINE bool IsInternetConnectionAvailable(void)
{
return this->status_info_task->IsInternetConnectionAvailable();
}
ALWAYS_INLINE const nxdt::tasks::TitleApplicationMetadataVector* GetApplicationMetadata(bool is_system)
{
return this->title_task->GetApplicationMetadata(is_system);
}
EVENT_SUBSCRIPTION(StatusInfoTask, StatusInfoEvent, status_info_task);
EVENT_SUBSCRIPTION(GameCardTask, GameCardStatusEvent, gc_status_task);
EVENT_SUBSCRIPTION(TitleTask, TitleEvent, title_task);

View file

@ -36,40 +36,40 @@ namespace nxdt::utils {
template<class F>
class ScopeGuard {
NON_COPYABLE(ScopeGuard);
private:
F f;
bool active;
public:
constexpr ALWAYS_INLINE ScopeGuard(F f) : f(std::move(f)), active(true) {}
constexpr ALWAYS_INLINE ~ScopeGuard()
{
if (active) f();
}
constexpr ALWAYS_INLINE void Cancel()
{
active = false;
}
constexpr ALWAYS_INLINE ScopeGuard(ScopeGuard&& rhs) : f(std::move(rhs.f)), active(rhs.active)
{
rhs.Cancel();
}
ScopeGuard &operator=(ScopeGuard&& rhs) = delete;
};
template<class F>
constexpr ALWAYS_INLINE ScopeGuard<F> MakeScopeGuard(F f)
{
return ScopeGuard<F>(std::move(f));
}
enum class ScopeGuardOnExit {};
template <typename F>
constexpr ALWAYS_INLINE ScopeGuard<F> operator+(ScopeGuardOnExit, F&& f)
{

View file

@ -47,20 +47,20 @@ namespace nxdt::tasks
NifmInternetConnectionType connection_type;
char *ip_addr;
} StatusInfoData;
/* Used to hold pointers to application metadata entries. */
typedef std::vector<TitleApplicationMetadata*> TitleApplicationMetadataVector;
/* Used to hold UMS devices. */
typedef std::vector<UsbHsFsDevice> UmsDeviceVector;
/* Custom event types. */
typedef brls::Event<const StatusInfoData*> StatusInfoEvent;
typedef brls::Event<GameCardStatus> GameCardStatusEvent;
typedef brls::Event<const TitleApplicationMetadataVector*> TitleEvent;
typedef brls::Event<const UmsDeviceVector*> UmsEvent;
typedef brls::Event<UsbHostSpeed> UsbHostEvent;
/* Status info task. */
/* Its event returns a pointer to a StatusInfoData struct. */
class StatusInfoTask: public brls::RepeatingTask
@ -68,19 +68,19 @@ namespace nxdt::tasks
private:
StatusInfoEvent status_info_event;
StatusInfoData status_info_data = {0};
protected:
void run(retro_time_t current_time) override;
public:
StatusInfoTask(void);
~StatusInfoTask(void);
bool IsInternetConnectionAvailable(void);
EVENT_SUBSCRIPTION(StatusInfoEvent, status_info_event);
};
/* Gamecard task. */
/* Its event returns a GameCardStatus value. */
class GameCardTask: public brls::RepeatingTask
@ -90,42 +90,42 @@ namespace nxdt::tasks
GameCardStatus cur_gc_status = GameCardStatus_NotInserted;
GameCardStatus prev_gc_status = GameCardStatus_NotInserted;
bool first_notification = true;
protected:
void run(retro_time_t current_time) override;
public:
GameCardTask(void);
~GameCardTask(void);
EVENT_SUBSCRIPTION(GameCardStatusEvent, gc_status_event);
};
/* Title task. */
/* Its event returns a pointer to a TitleApplicationMetadataVector with metadata for user titles (system titles don't change at runtime). */
class TitleTask: public brls::RepeatingTask
{
private:
TitleEvent title_event;
TitleApplicationMetadataVector system_metadata;
TitleApplicationMetadataVector user_metadata;
void PopulateApplicationMetadataVector(bool is_system);
protected:
void run(retro_time_t current_time) override;
public:
TitleTask(void);
~TitleTask(void);
/* Intentionally left here to let system titles views retrieve metadata. */
const TitleApplicationMetadataVector* GetApplicationMetadata(bool is_system);
EVENT_SUBSCRIPTION(TitleEvent, title_event);
};
/* USB Mass Storage task. */
/* Its event returns a pointer to a UmsDeviceVector. */
class UmsTask: public brls::RepeatingTask
@ -133,19 +133,19 @@ namespace nxdt::tasks
private:
UmsEvent ums_event;
UmsDeviceVector ums_devices;
void PopulateUmsDeviceVector(void);
protected:
void run(retro_time_t current_time) override;
public:
UmsTask(void);
~UmsTask(void);
EVENT_SUBSCRIPTION(UmsEvent, ums_event);
};
/* USB host device connection task. */
class UsbHostTask: public brls::RepeatingTask
{
@ -153,14 +153,14 @@ namespace nxdt::tasks
UsbHostEvent usb_host_event;
UsbHostSpeed cur_usb_host_speed = UsbHostSpeed_None;
UsbHostSpeed prev_usb_host_speed = UsbHostSpeed_None;
protected:
void run(retro_time_t current_time) override;
public:
UsbHostTask(void);
~UsbHostTask(void);
EVENT_SUBSCRIPTION(UsbHostEvent, usb_host_event);
};
}

View file

@ -35,46 +35,46 @@ namespace nxdt::views
private:
const TitleApplicationMetadata *app_metadata = nullptr;
bool is_system = false;
TitleUserApplicationData user_app_data = {0};
TitleInfo *system_title_info = NULL;
public:
TitlesTabPopup(const TitleApplicationMetadata *app_metadata, bool is_system);
~TitlesTabPopup(void);
};
/* Expanded ListItem class to hold application metadata. */
class TitlesTabItem: public brls::ListItem
{
private:
const TitleApplicationMetadata *app_metadata = nullptr;
bool is_system = false;
public:
TitlesTabItem(const TitleApplicationMetadata *app_metadata, bool is_system);
ALWAYS_INLINE const TitleApplicationMetadata *GetApplicationMetadata(void)
{
return this->app_metadata;
}
ALWAYS_INLINE bool IsSystemTitle(void)
{
return this->is_system;
}
};
class TitlesTab: public LayeredErrorFrame
{
private:
RootView *root_view = nullptr;
nxdt::tasks::TitleEvent::Subscription title_task_sub;
bool is_system = false;
void PopulateList(const nxdt::tasks::TitleApplicationMetadataVector* app_metadata);
public:
TitlesTab(RootView *root_view, bool is_system);
~TitlesTab(void);

File diff suppressed because it is too large Load diff

View file

@ -1,71 +0,0 @@
#pragma once
#ifndef __DUMPER_H__
#define __DUMPER_H__
#include <switch.h>
#include "util.h"
#define FAT32_FILESIZE_LIMIT (u64)0xFFFFFFFF // 4 GiB - 1 (4294967295 bytes)
#define SPLIT_FILE_XCI_PART_SIZE (u64)0xFFFF8000 // 4 GiB - 0x8000 (4294934528 bytes) (based on XCI-Cutter)
#define SPLIT_FILE_NSP_PART_SIZE (u64)0xFFFF0000 // 4 GiB - 0x10000 (4294901760 bytes) (based on splitNSP.py)
#define SPLIT_FILE_GENERIC_PART_SIZE SPLIT_FILE_NSP_PART_SIZE
#define SPLIT_FILE_SEQUENTIAL_SIZE (u64)0x40000000 // 1 GiB (used for sequential dumps when there's not enough storage space available)
#define CERT_OFFSET 0x7000
#define CERT_SIZE 0x200
typedef struct {
bool keepCert; // Original value for the "Keep certificate" option. Overrides the selected setting in the current session
bool trimDump; // Original value for the "Trim output dump" option. Overrides the selected setting in the current session
bool calcCrc; // "CRC32 checksum calculation + dump verification" option
u8 partNumber; // Next part number
u32 partitionIndex; // IStorage partition index
u64 partitionOffset; // IStorage partition offset
u32 certCrc; // CRC32 checksum accumulator (XCI with cert). Only used if calcCrc == true and keepCert == true
u32 certlessCrc; // CRC32 checksum accumulator (certless XCI). Only used if calcCrc == true
} PACKED sequentialXciCtx;
// This struct is followed by 'ncaCount' SHA-256 checksums in the output file and 'programNcaModCount' modified + reencrypted Program NCA headers
// The modified NCA headers are only needed if their NPDM signature is replaced (it uses cryptographically secure random numbers). The RSA public key used in the ACID section from the main.npdm file is constant, so we don't need to keep track of that
typedef struct {
NcmStorageId storageId; // Source storage from which the data is dumped
bool removeConsoleData; // Original value for the "Remove console specific data" option. Overrides the selected setting in the current session
bool tiklessDump; // Original value for the "Generate ticket-less dump" option. Overrides the selected setting in the current session. Ignored if removeConsoleData == false
bool npdmAcidRsaPatch; // Original value for the "Change NPDM RSA key/sig in Program NCA" option. Overrides the selected setting in the current session
bool preInstall; // Indicates if we're dealing with a preinstalled title - e.g. if the user already accepted the missing ticket prompt
u8 partNumber; // Next part number
u32 pfs0FileCount; // PFS0 file count
u32 ncaCount; // NCA count
u32 programNcaModCount; // Program NCA mod count
u32 fileIndex; // Current PFS0 file entry index
u64 fileOffset; // Current PFS0 file entry offset
Sha256Context hashCtx; // Current NCA SHA-256 checksum context. Only used when dealing with the same NCA between different parts
} PACKED sequentialNspCtx;
typedef struct {
bool enabled;
nspDumpType titleType;
u32 titleIndex;
u64 contentSize;
char *contentSizeStr;
char nspFilename[NAME_BUF_LEN];
char truncatedNspFilename[NAME_BUF_LEN];
} batchEntry;
bool dumpNXCardImage(xciOptions *xciDumpCfg);
int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleIndex, nspOptions *nspDumpCfg, bool batch);
int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg);
bool dumpRawHfs0Partition(u32 partition, bool doSplitting);
bool dumpHfs0PartitionData(u32 partition, bool doSplitting);
bool dumpFileFromHfs0Partition(u32 partition, u32 fileIndex, char *filename, bool doSplitting);
bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, ncaFsOptions *exeFsDumpCfg);
bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, ncaFsOptions *exeFsDumpCfg);
bool dumpRomFsSectionData(u32 titleIndex, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg);
bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg);
bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg);
bool dumpGameCardCertificate();
bool dumpTicketFromTitle(u32 titleIndex, selectedTicketType curTikType, ticketOptions *tikDumpCfg);
#endif

View file

@ -1,933 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <mbedtls/base64.h>
#include "keys.h"
#include "util.h"
#include "ui.h"
#include "rsa.h"
#include "nso.h"
/* Extern variables */
extern int breaks;
extern int font_height;
extern exefs_ctx_t exeFsContext;
extern romfs_ctx_t romFsContext;
extern bktr_ctx_t bktrContext;
extern nca_keyset_t nca_keyset;
extern u8 *ncaCtrBuf;
bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize)
{
if (!input || !outBuf || !outBufSize || outBufSize < NCA_FULL_HEADER_LENGTH || (__builtin_bswap32(input->magic) != NCA3_MAGIC && __builtin_bswap32(input->magic) != NCA2_MAGIC))
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA header encryption parameters.", __func__);
return false;
}
if (!loadNcaKeyset()) return false;
u32 i;
size_t crypt_res;
Aes128XtsContext hdr_aes_ctx;
u8 header_key_0[16];
u8 header_key_1[16];
memcpy(header_key_0, nca_keyset.header_key, 16);
memcpy(header_key_1, nca_keyset.header_key + 16, 16);
aes128XtsContextCreate(&hdr_aes_ctx, header_key_0, header_key_1, true);
if (__builtin_bswap32(input->magic) == NCA3_MAGIC)
{
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf, input, NCA_FULL_HEADER_LENGTH, 0, true);
if (crypt_res != NCA_FULL_HEADER_LENGTH)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header! (%u != %lu)", __func__, NCA_FULL_HEADER_LENGTH, crypt_res);
return false;
}
} else
if (__builtin_bswap32(input->magic) == NCA2_MAGIC)
{
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf, input, NCA_HEADER_LENGTH, 0, true);
if (crypt_res != NCA_HEADER_LENGTH)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header! (%u != %lu)", __func__, NCA_HEADER_LENGTH, crypt_res);
return false;
}
for(i = 0; i < NCA_SECTION_HEADER_CNT; i++)
{
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf + NCA_HEADER_LENGTH + (i * NCA_SECTION_HEADER_LENGTH), &(input->fs_headers[i]), NCA_SECTION_HEADER_LENGTH, 0, true);
if (crypt_res != NCA_SECTION_HEADER_LENGTH)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header section #%u! (%u != %lu)", __func__, i, NCA_SECTION_HEADER_LENGTH, crypt_res);
return false;
}
}
} else {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid decrypted NCA magic word! (0x%08X)", __func__, __builtin_bswap32(input->magic));
return false;
}
return true;
}
bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title_rights_ctx *rights_info, u8 *decrypted_nca_keys, bool retrieveTitleKeyData)
{
if (!ncaBuf || !ncaBufSize || ncaBufSize < NCA_FULL_HEADER_LENGTH || !out || !decrypted_nca_keys)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA header decryption parameters!", __func__);
return false;
}
if (!loadNcaKeyset()) return false;
int ret;
u32 i;
size_t crypt_res;
Aes128XtsContext hdr_aes_ctx;
u8 header_key_0[16];
u8 header_key_1[16];
bool has_rights_id = false;
memcpy(header_key_0, nca_keyset.header_key, 16);
memcpy(header_key_1, nca_keyset.header_key + 16, 16);
aes128XtsContextCreate(&hdr_aes_ctx, header_key_0, header_key_1, false);
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, out, ncaBuf, NCA_HEADER_LENGTH, 0, false);
if (crypt_res != NCA_HEADER_LENGTH)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header! (%u != %lu)", __func__, NCA_HEADER_LENGTH, crypt_res);
return false;
}
if (__builtin_bswap32(out->magic) == NCA3_MAGIC)
{
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, out, ncaBuf, NCA_FULL_HEADER_LENGTH, 0, false);
if (crypt_res != NCA_FULL_HEADER_LENGTH)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header! (%u != %lu)", __func__, NCA_FULL_HEADER_LENGTH, crypt_res);
return false;
}
} else
if (__builtin_bswap32(out->magic) == NCA2_MAGIC)
{
for(i = 0; i < NCA_SECTION_HEADER_CNT; i++)
{
if (out->fs_headers[i]._0x148[0] != 0 || memcmp(out->fs_headers[i]._0x148, out->fs_headers[i]._0x148 + 1, 0xB7))
{
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(out->fs_headers[i]), ncaBuf + NCA_HEADER_LENGTH + (i * NCA_SECTION_HEADER_LENGTH), NCA_SECTION_HEADER_LENGTH, 0, false);
if (crypt_res != NCA_SECTION_HEADER_LENGTH)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header section #%u! (%u != %lu)", __func__, i, NCA_SECTION_HEADER_LENGTH, crypt_res);
return false;
}
} else {
memset(&(out->fs_headers[i]), 0, sizeof(nca_fs_header_t));
}
}
} else {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA magic word! Wrong header key? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(out->magic));
return false;
}
for(i = 0; i < 0x10; i++)
{
if (out->rights_id[i] != 0)
{
has_rights_id = true;
break;
}
}
if (has_rights_id)
{
if (rights_info != NULL)
{
// If we're dealing with a rights info context, retrieve the ticket for the current title
if (!rights_info->has_rights_id)
{
rights_info->has_rights_id = true;
memcpy(rights_info->rights_id, out->rights_id, 16);
convertDataToHexString(out->rights_id, 16, rights_info->rights_id_str, 33);
sprintf(rights_info->tik_filename, "%s.tik", rights_info->rights_id_str);
sprintf(rights_info->cert_filename, "%s.cert", rights_info->rights_id_str);
if (retrieveTitleKeyData)
{
ret = retrieveNcaTikTitleKey(out, (u8*)(&(rights_info->tik_data)), rights_info->enc_titlekey, rights_info->dec_titlekey);
if (ret >= 0)
{
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10);
rights_info->retrieved_tik = true;
} else {
if (ret == -2)
{
// We are probably dealing with a pre-installed title
// Let's enable our missing ticket flag - we'll use it to display a prompt asking the user if they want to proceed anyway
rights_info->missing_tik = true;
} else {
return false;
}
}
}
} else {
// Copy what we already have
if (retrieveTitleKeyData && rights_info->retrieved_tik)
{
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10);
}
}
} else {
// Otherwise, only retrieve the decrypted titlekey. This is used with ExeFS/RomFS section parsing for SD/eMMC titles
if (retrieveTitleKeyData)
{
u8 tmp_dec_titlekey[0x10];
if (retrieveNcaTikTitleKey(out, NULL, NULL, tmp_dec_titlekey) < 0) return false;
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), tmp_dec_titlekey, 0x10);
}
}
} else {
if (!decryptNcaKeyArea(out, decrypted_nca_keys)) return false;
}
return true;
}
bool retrieveTitleKeyFromGameCardTicket(title_rights_ctx *rights_info, u8 *decrypted_nca_keys)
{
if (!rights_info || !rights_info->has_rights_id || !strlen(rights_info->tik_filename) || !decrypted_nca_keys)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve titlekey from gamecard ticket!", __func__);
return false;
}
// Check if the ticket has already been retrieved from the HFS0 partition in the gamecard
if (rights_info->retrieved_tik)
{
// Save the decrypted NCA key area keys
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10);
return true;
}
// Load external keys
if (!loadExternalKeys()) return false;
// Retrieve ticket
if (!readFileFromSecureHfs0PartitionByName(rights_info->tik_filename, 0, &(rights_info->tik_data), ETICKET_TIK_FILE_SIZE)) return false;
// Save encrypted titlekey
memcpy(rights_info->enc_titlekey, rights_info->tik_data.titlekey_block, 0x10);
// Decrypt titlekey
u8 crypto_type = rights_info->rights_id[0x0F];
if (crypto_type) crypto_type--;
if (crypto_type >= 0x20)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA keyblob index.", __func__);
return false;
}
Aes128Context titlekey_aes_ctx;
aes128ContextCreate(&titlekey_aes_ctx, nca_keyset.titlekeks[crypto_type], false);
aes128DecryptBlock(&titlekey_aes_ctx, rights_info->dec_titlekey, rights_info->enc_titlekey);
// Update retrieved ticket flag
rights_info->retrieved_tik = true;
// Save the decrypted NCA key area keys
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10);
return true;
}
bool processProgramNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data **output, u32 *cur_mod_cnt, u32 idx)
{
if (!ncmStorage || !ncaId || !dec_nca_header || !xml_content_info || !output || !cur_mod_cnt)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to process Program NCA!", __func__);
return false;
}
if (dec_nca_header->fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_nca_header->fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 doesn't hold a PFS0 partition!", __func__);
return false;
}
if (!dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid size for PFS0 partition in Program NCA section #0!", __func__);
return false;
}
if (dec_nca_header->fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_CTR)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for Program NCA section #0! (0x%02X)", __func__, dec_nca_header->fs_headers[0].crypt_type);
return false;
}
u32 i;
u64 section_offset;
u64 hash_table_offset;
u64 nca_pfs0_offset;
pfs0_header nca_pfs0_header;
pfs0_file_entry *nca_pfs0_entries = NULL;
u64 nca_pfs0_data_offset;
npdm_t npdm_header;
bool found_meta = false;
u64 meta_offset;
u64 acid_pubkey_offset;
u64 block_hash_table_offset;
u64 block_hash_table_end_offset;
u64 block_start_offset[2] = { 0, 0 };
u64 block_size[2] = { 0, 0 };
u8 block_hash[2][SHA256_HASH_SIZE];
u8 *block_data[2] = { NULL, NULL };
u64 sig_write_size[2] = { 0, 0 };
u8 *hash_table = NULL;
Aes128CtrContext aes_ctx;
section_offset = ((u64)dec_nca_header->section_entries[0].media_start_offset * (u64)MEDIA_UNIT_SIZE);
hash_table_offset = (section_offset + dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_offset);
nca_pfs0_offset = (section_offset + dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_offset);
if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !hash_table_offset || hash_table_offset < section_offset || !nca_pfs0_offset || nca_pfs0_offset <= hash_table_offset)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offsets for Program NCA section #0!", __func__);
return false;
}
// Generate initial CTR
unsigned char ctr[0x10];
u64 ofs = (section_offset >> 4);
for(i = 0; i < 0x8; i++)
{
ctr[i] = dec_nca_header->fs_headers[0].section_ctr[0x08 - i - 1];
ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF);
ofs >>= 8;
}
u8 ctr_key[NCA_KEY_AREA_KEY_SIZE];
memcpy(ctr_key, xml_content_info->decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE);
aes128CtrContextCreate(&aes_ctx, ctr_key, ctr);
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset, &nca_pfs0_header, sizeof(pfs0_header), false))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition header!", __func__);
return false;
}
if (__builtin_bswap32(nca_pfs0_header.magic) != PFS0_MAGIC)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid magic word for Program NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(nca_pfs0_header.magic));
return false;
}
if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__);
return false;
}
nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry));
if (!nca_pfs0_entries)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 partition entries!", __func__);
return false;
}
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset + sizeof(pfs0_header), nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry), false))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition entries!", __func__);
free(nca_pfs0_entries);
return false;
}
nca_pfs0_data_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry)) + (u64)nca_pfs0_header.str_table_size);
// Looking for META magic
for(i = 0; i < nca_pfs0_header.file_cnt; i++)
{
u64 nca_pfs0_cur_file_offset = (nca_pfs0_data_offset + nca_pfs0_entries[i].file_offset);
// Read and decrypt NPDM header
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_cur_file_offset, &npdm_header, sizeof(npdm_t), false))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 entry #%u!", __func__, i);
free(nca_pfs0_entries);
return false;
}
if (__builtin_bswap32(npdm_header.magic) == META_MAGIC)
{
found_meta = true;
meta_offset = nca_pfs0_cur_file_offset;
acid_pubkey_offset = (meta_offset + (u64)npdm_header.acid_offset + (u64)NPDM_SIGNATURE_SIZE);
break;
}
}
free(nca_pfs0_entries);
if (!found_meta)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find NPDM entry in Program NCA section #0 PFS0 partition!", __func__);
return false;
}
// Calculate block offsets
block_hash_table_offset = (hash_table_offset + (((acid_pubkey_offset - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size)) * (u64)SHA256_HASH_SIZE);
block_hash_table_end_offset = (hash_table_offset + (((acid_pubkey_offset + (u64)NPDM_SIGNATURE_SIZE - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size) * (u64)SHA256_HASH_SIZE));
block_start_offset[0] = (nca_pfs0_offset + (((acid_pubkey_offset - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size) * (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size));
// Make sure our block doesn't pass PFS0 end offset
if ((block_start_offset[0] - nca_pfs0_offset + dec_nca_header->fs_headers[0].pfs0_superblock.block_size) > dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size)
{
block_size[0] = (dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size - (block_start_offset[0] - nca_pfs0_offset));
} else {
block_size[0] = (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size;
}
block_data[0] = malloc(block_size[0]);
if (!block_data[0])
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 0!", __func__);
return false;
}
// Read and decrypt block
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[0], block_data[0], block_size[0], false))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 NPDM block 0!", __func__);
free(block_data[0]);
return false;
}
// Make sure that 1 block will cover all patched bytes, otherwise we'll have to recalculate another hash block
if (block_hash_table_offset != block_hash_table_end_offset)
{
sig_write_size[1] = (acid_pubkey_offset - block_start_offset[0] + (u64)NPDM_SIGNATURE_SIZE - block_size[0]);
sig_write_size[0] = ((u64)NPDM_SIGNATURE_SIZE - sig_write_size[1]);
} else {
sig_write_size[0] = (u64)NPDM_SIGNATURE_SIZE;
}
// Patch ACID public key changing it to a self-generated pubkey
memcpy(block_data[0] + (acid_pubkey_offset - block_start_offset[0]), rsa_get_public_key(), sig_write_size[0]);
// Calculate new block hash
sha256CalculateHash(block_hash[0], block_data[0], block_size[0]);
if (block_hash_table_offset != block_hash_table_end_offset)
{
block_start_offset[1] = (nca_pfs0_offset + (((acid_pubkey_offset + (u64)NPDM_SIGNATURE_SIZE - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size) * (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size));
if ((block_start_offset[1] - nca_pfs0_offset + dec_nca_header->fs_headers[0].pfs0_superblock.block_size) > dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size)
{
block_size[1] = (dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size - (block_start_offset[1] - nca_pfs0_offset));
} else {
block_size[1] = (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size;
}
block_data[1] = malloc(block_size[1]);
if (!block_data[1])
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 1!", __func__);
free(block_data[0]);
return false;
}
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[1], block_data[1], block_size[1], false))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 NPDM block 1!", __func__);
free(block_data[0]);
free(block_data[1]);
return false;
}
memcpy(block_data[1], rsa_get_public_key() + sig_write_size[0], sig_write_size[1]);
sha256CalculateHash(block_hash[1], block_data[1], block_size[1]);
}
hash_table = malloc(dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size);
if (!hash_table)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 hash table!", __func__);
free(block_data[0]);
if (block_data[1]) free(block_data[1]);
return false;
}
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, hash_table_offset, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size, false))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 hash table!", __func__);
free(block_data[0]);
if (block_data[1]) free(block_data[1]);
free(hash_table);
return false;
}
// Update block hashes
memcpy(hash_table + (block_hash_table_offset - hash_table_offset), block_hash[0], SHA256_HASH_SIZE);
if (block_hash_table_offset != block_hash_table_end_offset) memcpy(hash_table + (block_hash_table_end_offset - hash_table_offset), block_hash[1], SHA256_HASH_SIZE);
// Calculate PFS0 superblock master hash
sha256CalculateHash(dec_nca_header->fs_headers[0].pfs0_superblock.master_hash, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size);
// Calculate section hash
sha256CalculateHash(dec_nca_header->section_hashes[0], &(dec_nca_header->fs_headers[0]), sizeof(nca_fs_header_t));
// Recreate NPDM signature
if (!rsa_sign(&(dec_nca_header->magic), NPDM_SIGNATURE_AREA_SIZE, dec_nca_header->npdm_key_sig, NPDM_SIGNATURE_SIZE))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to recreate Program NCA NPDM signature!", __func__);
free(block_data[0]);
if (block_data[1]) free(block_data[1]);
free(hash_table);
return false;
}
// Reencrypt relevant data blocks
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[0], block_data[0], block_size[0], true))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 NPDM block 0!", __func__);
free(block_data[0]);
if (block_data[1]) free(block_data[1]);
free(hash_table);
return false;
}
if (block_hash_table_offset != block_hash_table_end_offset)
{
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[1], block_data[1], block_size[1], true))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 NPDM block 1!", __func__);
free(block_data[0]);
free(block_data[1]);
free(hash_table);
return false;
}
}
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, hash_table_offset, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size, true))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 hash table!", __func__);
free(block_data[0]);
if (block_data[1]) free(block_data[1]);
free(hash_table);
return false;
}
// Save data to the output struct so we can write it later
// The caller function must free these data pointers
nca_program_mod_data *tmp_mod_data = realloc(*output, (*cur_mod_cnt + 1) * sizeof(nca_program_mod_data));
if (!tmp_mod_data)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to reallocate Program NCA mod data buffer!", __func__);
free(block_data[0]);
if (block_data[1]) free(block_data[1]);
free(hash_table);
return false;
}
memset(&(tmp_mod_data[*cur_mod_cnt]), 0, sizeof(nca_program_mod_data));
tmp_mod_data[*cur_mod_cnt].nca_index = idx;
tmp_mod_data[*cur_mod_cnt].hash_table = hash_table;
tmp_mod_data[*cur_mod_cnt].hash_table_offset = hash_table_offset;
tmp_mod_data[*cur_mod_cnt].hash_table_size = dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size;
tmp_mod_data[*cur_mod_cnt].block_mod_cnt = (block_hash_table_offset != block_hash_table_end_offset ? 2 : 1);
tmp_mod_data[*cur_mod_cnt].block_data[0] = block_data[0];
tmp_mod_data[*cur_mod_cnt].block_offset[0] = block_start_offset[0];
tmp_mod_data[*cur_mod_cnt].block_size[0] = block_size[0];
if (block_hash_table_offset != block_hash_table_end_offset)
{
tmp_mod_data[*cur_mod_cnt].block_data[1] = block_data[1];
tmp_mod_data[*cur_mod_cnt].block_offset[1] = block_start_offset[1];
tmp_mod_data[*cur_mod_cnt].block_size[1] = block_size[1];
}
*output = tmp_mod_data;
tmp_mod_data = NULL;
*cur_mod_cnt += 1;
return true;
}
bool retrieveCnmtNcaData(NcmStorageId curStorageId, u8 *ncaBuf, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, u32 cnmtNcaIndex, nca_cnmt_mod_data *output, title_rights_ctx *rights_info)
{
if (!ncaBuf || !xml_program_info || !xml_content_info || !output || !rights_info)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve CNMT NCA!", __func__);
return false;
}
nca_header_t dec_header;
u32 i, j, k = 0;
u64 section_offset;
u64 section_size;
u8 *section_data = NULL;
Aes128CtrContext aes_ctx;
u64 nca_pfs0_offset;
u64 nca_pfs0_str_table_offset;
u64 nca_pfs0_data_offset;
pfs0_header nca_pfs0_header;
pfs0_file_entry *nca_pfs0_entries = NULL;
bool found_cnmt = false;
u64 title_cnmt_offset;
u64 title_cnmt_size;
cnmt_header title_cnmt_header;
cnmt_extended_header title_cnmt_extended_header;
u64 digest_offset;
// Generate filename for our required CNMT file
char cnmtFileName[50] = {'\0'};
snprintf(cnmtFileName, MAX_CHARACTERS(cnmtFileName), "%s_%016lx.cnmt", getTitleType(xml_program_info->type), xml_program_info->title_id);
// Decrypt the NCA header
// Don't retrieve the ticket and/or titlekey if we're dealing with a Patch with titlekey crypto bundled with the inserted gamecard
if (!decryptNcaHeader(ncaBuf, xml_content_info[cnmtNcaIndex].size, &dec_header, rights_info, xml_content_info[cnmtNcaIndex].decrypted_nca_keys, (curStorageId != NcmStorageId_GameCard))) return false;
if (dec_header.fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_header.fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: CNMT NCA section #0 doesn't hold a PFS0 partition!", __func__);
return false;
}
if (!dec_header.fs_headers[0].pfs0_superblock.pfs0_size)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid size for PFS0 partition in CNMT NCA section #0!", __func__);
return false;
}
if (dec_header.fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_CTR)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for CNMT NCA section #0! (0x%02X)", __func__, dec_header.fs_headers[0].crypt_type);
return false;
}
bool has_rights_id = false;
for(i = 0; i < 0x10; i++)
{
if (dec_header.rights_id[i] != 0)
{
has_rights_id = true;
break;
}
}
// CNMT NCAs never use titlekey crypto
if (has_rights_id)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Rights ID field in CNMT NCA header not empty!", __func__);
return false;
}
// Modify distribution type
if (curStorageId == NcmStorageId_GameCard) dec_header.distribution = 0;
section_offset = ((u64)dec_header.section_entries[0].media_start_offset * (u64)MEDIA_UNIT_SIZE);
section_size = (((u64)dec_header.section_entries[0].media_end_offset * (u64)MEDIA_UNIT_SIZE) - section_offset);
if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !section_size)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for CNMT NCA section #0!", __func__);
return false;
}
// Generate initial CTR
unsigned char ctr[0x10];
u64 ofs = (section_offset >> 4);
for(i = 0; i < 0x8; i++)
{
ctr[i] = dec_header.fs_headers[0].section_ctr[0x08 - i - 1];
ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF);
ofs >>= 8;
}
u8 ctr_key[NCA_KEY_AREA_KEY_SIZE];
memcpy(ctr_key, xml_content_info[cnmtNcaIndex].decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE);
aes128CtrContextCreate(&aes_ctx, ctr_key, ctr);
section_data = malloc(section_size);
if (!section_data)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the decrypted CNMT NCA section #0!", __func__);
return false;
}
aes128CtrCrypt(&aes_ctx, section_data, ncaBuf + section_offset, section_size);
nca_pfs0_offset = dec_header.fs_headers[0].pfs0_superblock.pfs0_offset;
memcpy(&nca_pfs0_header, section_data + nca_pfs0_offset, sizeof(pfs0_header));
if (__builtin_bswap32(nca_pfs0_header.magic) != PFS0_MAGIC)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid magic word for CNMT NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(nca_pfs0_header.magic));
free(section_data);
return false;
}
if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: CNMT NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__);
free(section_data);
return false;
}
nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry));
if (!nca_pfs0_entries)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for CNMT NCA section #0 PFS0 partition entries!", __func__);
free(section_data);
return false;
}
memcpy(nca_pfs0_entries, section_data + nca_pfs0_offset + sizeof(pfs0_header), (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry));
nca_pfs0_str_table_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry)));
nca_pfs0_data_offset = (nca_pfs0_str_table_offset + (u64)nca_pfs0_header.str_table_size);
// Look for the CNMT
for(i = 0; i < nca_pfs0_header.file_cnt; i++)
{
u64 filename_offset = (nca_pfs0_str_table_offset + nca_pfs0_entries[i].filename_offset);
if (!strncasecmp((char*)section_data + filename_offset, cnmtFileName, strlen(cnmtFileName)))
{
found_cnmt = true;
title_cnmt_offset = (nca_pfs0_data_offset + nca_pfs0_entries[i].file_offset);
title_cnmt_size = nca_pfs0_entries[i].file_size;
break;
}
}
free(nca_pfs0_entries);
if (!found_cnmt)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find file \"%s\" in PFS0 partition from CNMT NCA section #0!", __func__, cnmtFileName);
free(section_data);
return false;
}
memcpy(&title_cnmt_header, section_data + title_cnmt_offset, sizeof(cnmt_header));
memcpy(&title_cnmt_extended_header, section_data + title_cnmt_offset + sizeof(cnmt_header), sizeof(cnmt_extended_header));
// Fill information for our CNMT XML
digest_offset = (title_cnmt_offset + title_cnmt_size - (u64)SHA256_HASH_SIZE);
memcpy(xml_program_info->digest, section_data + digest_offset, SHA256_HASH_SIZE);
convertDataToHexString(xml_program_info->digest, SHA256_HASH_SIZE, xml_program_info->digest_str, (SHA256_HASH_SIZE * 2) + 1);
xml_content_info[cnmtNcaIndex].keyblob = (dec_header.crypto_type2 > dec_header.crypto_type ? dec_header.crypto_type2 : dec_header.crypto_type);
xml_program_info->required_dl_sysver = title_cnmt_header.required_dl_sysver;
xml_program_info->min_keyblob = (rights_info->has_rights_id ? rights_info->rights_id[15] : xml_content_info[cnmtNcaIndex].keyblob);
xml_program_info->min_sysver = title_cnmt_extended_header.min_sysver;
xml_program_info->patch_tid = title_cnmt_extended_header.patch_tid;
xml_program_info->min_appver = title_cnmt_extended_header.min_appver;
// Retrieve the ID offset and content record offset for each of our NCAs (except the CNMT NCA)
// Also wipe each of the content records we're gonna replace
for(i = 0; i < (xml_program_info->nca_cnt - 1); i++) // Discard CNMT NCA
{
for(j = 0; j < title_cnmt_header.content_cnt; j++)
{
cnmt_content_record cnt_record;
memcpy(&cnt_record, section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size + (j * sizeof(cnmt_content_record)), sizeof(cnmt_content_record));
if (memcmp(xml_content_info[i].nca_id, cnt_record.nca_id, SHA256_HASH_SIZE / 2) != 0) continue;
// Save content record offset
xml_content_info[i].cnt_record_offset = (j * sizeof(cnmt_content_record));
// Empty CNMT content record
memset(section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size + (j * sizeof(cnmt_content_record)), 0, sizeof(cnmt_content_record));
// Increase counter
k++;
break;
}
}
// Verify counter
if (k != (xml_program_info->nca_cnt - 1))
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid content record entries in the CNMT NCA!", __func__);
free(section_data);
return false;
}
// Replace input buffer data in-place
memcpy(ncaBuf, &dec_header, NCA_FULL_HEADER_LENGTH);
memcpy(ncaBuf + section_offset, section_data, section_size);
free(section_data);
// Update offsets
nca_pfs0_offset += section_offset;
title_cnmt_offset += section_offset;
// Save data to output struct
output->section_offset = section_offset;
output->section_size = section_size;
output->hash_table_offset = (section_offset + dec_header.fs_headers[0].pfs0_superblock.hash_table_offset);
output->hash_block_size = dec_header.fs_headers[0].pfs0_superblock.block_size;
output->hash_block_cnt = (dec_header.fs_headers[0].pfs0_superblock.hash_table_size / SHA256_HASH_SIZE);
output->pfs0_offset = nca_pfs0_offset;
output->pfs0_size = dec_header.fs_headers[0].pfs0_superblock.pfs0_size;
output->title_cnmt_offset = title_cnmt_offset;
output->title_cnmt_size = title_cnmt_size;
return true;
}
bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *cnmt_mod)
{
if (!ncaBuf || !ncaBufSize || !xml_program_info || xml_program_info->nca_cnt <= 1 || !xml_content_info || !cnmt_mod)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to patch CNMT NCA!", __func__);
return false;
}
u32 i;
u32 nca_cnt = (xml_program_info->nca_cnt - 1); // Discard CNMT NCA
cnmt_header title_cnmt_header;
cnmt_content_record title_cnmt_content_record;
u64 title_cnmt_content_records_offset;
nca_header_t dec_header;
Aes128CtrContext aes_ctx;
// Copy CNMT header
memcpy(&title_cnmt_header, ncaBuf + cnmt_mod->title_cnmt_offset, sizeof(cnmt_header));
// Calculate the start offset for the content records
title_cnmt_content_records_offset = (cnmt_mod->title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size);
// Write content records
for(i = 0; i < nca_cnt; i++)
{
memset(&title_cnmt_content_record, 0, sizeof(cnmt_content_record));
memcpy(title_cnmt_content_record.hash, xml_content_info[i].hash, SHA256_HASH_SIZE);
memcpy(title_cnmt_content_record.nca_id, xml_content_info[i].nca_id, SHA256_HASH_SIZE / 2);
convertU64ToNcaSize(xml_content_info[i].size, title_cnmt_content_record.size);
title_cnmt_content_record.type = xml_content_info[i].type;
title_cnmt_content_record.id_offset = xml_content_info[i].id_offset;
memcpy(ncaBuf + title_cnmt_content_records_offset + xml_content_info[i].cnt_record_offset, &title_cnmt_content_record, sizeof(cnmt_content_record));
}
// Recalculate block hashes
for(i = 0; i < cnmt_mod->hash_block_cnt; i++)
{
u64 blk_offset = ((u64)i * cnmt_mod->hash_block_size);
u64 rest_size = (cnmt_mod->pfs0_size - blk_offset);
u64 blk_size = (rest_size > cnmt_mod->hash_block_size ? cnmt_mod->hash_block_size : rest_size);
sha256CalculateHash(ncaBuf + cnmt_mod->hash_table_offset + (i * SHA256_HASH_SIZE), ncaBuf + cnmt_mod->pfs0_offset + blk_offset, blk_size);
}
// Copy header to struct
memcpy(&dec_header, ncaBuf, sizeof(nca_header_t));
// Calculate PFS0 superblock master hash
sha256CalculateHash(dec_header.fs_headers[0].pfs0_superblock.master_hash, ncaBuf + cnmt_mod->hash_table_offset, dec_header.fs_headers[0].pfs0_superblock.hash_table_size);
// Calculate section hash
sha256CalculateHash(dec_header.section_hashes[0], &(dec_header.fs_headers[0]), sizeof(nca_fs_header_t));
// Generate initial CTR
unsigned char ctr[0x10];
u64 ofs = (cnmt_mod->section_offset >> 4);
for(i = 0; i < 0x8; i++)
{
ctr[i] = dec_header.fs_headers[0].section_ctr[0x08 - i - 1];
ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF);
ofs >>= 8;
}
u8 ctr_key[NCA_KEY_AREA_KEY_SIZE];
memcpy(ctr_key, xml_content_info[xml_program_info->nca_cnt - 1].decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE);
aes128CtrContextCreate(&aes_ctx, ctr_key, ctr);
// Reencrypt CNMT NCA
if (!encryptNcaHeader(&dec_header, ncaBuf, ncaBufSize))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt modified CNMT NCA header!", __func__);
return false;
}
aes128CtrCrypt(&aes_ctx, ncaBuf + cnmt_mod->section_offset, ncaBuf + cnmt_mod->section_offset, cnmt_mod->section_size);
// Calculate CNMT NCA SHA-256 checksum and fill information for our CNMT XML
sha256CalculateHash(xml_content_info[xml_program_info->nca_cnt - 1].hash, ncaBuf, ncaBufSize);
convertDataToHexString(xml_content_info[xml_program_info->nca_cnt - 1].hash, SHA256_HASH_SIZE, xml_content_info[xml_program_info->nca_cnt - 1].hash_str, (SHA256_HASH_SIZE * 2) + 1);
memcpy(xml_content_info[xml_program_info->nca_cnt - 1].nca_id, xml_content_info[xml_program_info->nca_cnt - 1].hash, SHA256_HASH_SIZE / 2);
convertDataToHexString(xml_content_info[xml_program_info->nca_cnt - 1].nca_id, SHA256_HASH_SIZE / 2, xml_content_info[xml_program_info->nca_cnt - 1].nca_id_str, SHA256_HASH_SIZE + 1);
return true;
}

View file

@ -1,368 +0,0 @@
#pragma once
#ifndef __NCA_H__
#define __NCA_H__
#include <switch.h>
#define NCA3_MAGIC (u32)0x4E434133 // "NCA3"
#define NCA2_MAGIC (u32)0x4E434132 // "NCA2"
#define NCA_HEADER_LENGTH 0x400
#define NCA_SECTION_HEADER_LENGTH 0x200
#define NCA_SECTION_HEADER_CNT 4
#define NCA_FULL_HEADER_LENGTH (NCA_HEADER_LENGTH + (NCA_SECTION_HEADER_LENGTH * NCA_SECTION_HEADER_CNT))
#define NCA_AES_XTS_SECTOR_SIZE 0x200
#define NCA_KEY_AREA_KEY_CNT 4
#define NCA_KEY_AREA_KEY_SIZE 0x10
#define NCA_KEY_AREA_SIZE (NCA_KEY_AREA_KEY_CNT * NCA_KEY_AREA_KEY_SIZE)
#define NCA_FS_HEADER_PARTITION_PFS0 0x01
#define NCA_FS_HEADER_FSTYPE_PFS0 0x02
#define NCA_FS_HEADER_PARTITION_ROMFS 0x00
#define NCA_FS_HEADER_FSTYPE_ROMFS 0x03
#define NCA_FS_HEADER_CRYPT_NONE 0x01
#define NCA_FS_HEADER_CRYPT_XTS 0x02
#define NCA_FS_HEADER_CRYPT_CTR 0x03
#define NCA_FS_HEADER_CRYPT_BKTR 0x04
#define PFS0_MAGIC (u32)0x50465330 // "PFS0"
#define IVFC_MAGIC (u32)0x49564643 // "IVFC"
#define IVFC_MAX_LEVEL 6
#define BKTR_MAGIC (u32)0x424B5452 // "BKTR"
#define ROMFS_HEADER_SIZE 0x50
#define ROMFS_ENTRY_EMPTY (u32)0xFFFFFFFF
#define ROMFS_NONAME_DIRENTRY_SIZE 0x18
#define ROMFS_NONAME_FILEENTRY_SIZE 0x20
#define ROMFS_ENTRY_DIR 1
#define ROMFS_ENTRY_FILE 2
#define META_MAGIC (u32)0x4D455441 // "META"
#define NPDM_SIGNATURE_SIZE 0x100
#define NPDM_SIGNATURE_AREA_SIZE 0x200
#define NSP_NCA_FILENAME_LENGTH 0x25 // NCA ID + ".nca" + NULL terminator
#define NSP_CNMT_FILENAME_LENGTH 0x2A // NCA ID + ".cnmt.nca" / ".cnmt.xml" + NULL terminator
#define NSP_PROGRAM_XML_FILENAME_LENGTH 0x31 // NCA ID + ".programinfo.xml" + NULL terminator
#define NSP_NACP_XML_FILENAME_LENGTH 0x2A // NCA ID + ".nacp.xml" + NULL terminator
#define NSP_LEGAL_XML_FILENAME_LENGTH 0x2F // NCA ID + ".legalinfo.xml" + NULL terminator
#define NSP_TIK_FILENAME_LENGTH 0x25 // Rights ID + ".tik" + NULL terminator
#define NSP_CERT_FILENAME_LENGTH 0x26 // Rights ID + ".cert" + NULL terminator
#define ETICKET_ENTRY_SIZE 0x400
#define ETICKET_TITLEKEY_OFFSET 0x180
#define ETICKET_RIGHTSID_OFFSET 0x2A0
#define ETICKET_UNKNOWN_FIELD_SIZE 0x140
#define ETICKET_DATA_OFFSET 0x140
#define ETICKET_CA_CERT_SIZE 0x400
#define ETICKET_XS_CERT_SIZE 0x300
#define ETICKET_TIK_FILE_SIZE (ETICKET_ENTRY_SIZE - 0x140)
#define ETICKET_CERT_FILE_SIZE (ETICKET_CA_CERT_SIZE + ETICKET_XS_CERT_SIZE)
#define ETICKET_TITLEKEY_COMMON 0
#define ETICKET_TITLEKEY_PERSONALIZED 1
typedef enum {
DUMP_APP_NSP = 0,
DUMP_PATCH_NSP,
DUMP_ADDON_NSP
} nspDumpType;
typedef struct {
u32 magic;
u32 file_cnt;
u32 str_table_size;
u32 reserved;
} PACKED pfs0_header;
typedef struct {
u64 file_offset;
u64 file_size;
u32 filename_offset;
u32 reserved;
} PACKED pfs0_file_entry;
typedef struct {
u32 media_start_offset;
u32 media_end_offset;
u8 _0x8[0x8]; /* Padding. */
} PACKED nca_section_entry_t;
typedef struct {
u8 master_hash[0x20]; /* SHA-256 hash of the hash table. */
u32 block_size; /* In bytes. */
u32 always_2;
u64 hash_table_offset; /* Normally zero. */
u64 hash_table_size;
u64 pfs0_offset;
u64 pfs0_size;
u8 _0x48[0xF0];
} PACKED pfs0_superblock_t;
typedef struct {
u64 logical_offset;
u64 hash_data_size;
u32 block_size;
u32 reserved;
} PACKED ivfc_level_hdr_t;
typedef struct {
u32 magic;
u32 id;
u32 master_hash_size;
u32 num_levels;
ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL];
u8 _0xA0[0x20];
u8 master_hash[0x20];
} PACKED ivfc_hdr_t;
typedef struct {
ivfc_hdr_t ivfc_header;
u8 _0xE0[0x58];
} PACKED romfs_superblock_t;
typedef struct {
u64 offset;
u64 size;
u32 magic; /* "BKTR" */
u32 _0x14; /* Version? */
u32 num_entries;
u32 _0x1C; /* Reserved? */
} PACKED bktr_header_t;
typedef struct {
ivfc_hdr_t ivfc_header;
u8 _0xE0[0x18];
bktr_header_t relocation_header;
bktr_header_t subsection_header;
} PACKED bktr_superblock_t;
/* NCA FS header. */
typedef struct {
u8 _0x0;
u8 _0x1;
u8 partition_type;
u8 fs_type;
u8 crypt_type;
u8 _0x5[0x3];
union { /* FS-specific superblock. Size = 0x138. */
pfs0_superblock_t pfs0_superblock;
romfs_superblock_t romfs_superblock;
bktr_superblock_t bktr_superblock;
};
union {
u8 section_ctr[0x8];
struct {
u32 section_ctr_low;
u32 section_ctr_high;
};
};
u8 _0x148[0xB8]; /* Padding. */
} PACKED nca_fs_header_t;
/* Nintendo content archive header. */
typedef struct {
u8 fixed_key_sig[0x100]; /* RSA-PSS signature over header with fixed key. */
u8 npdm_key_sig[0x100]; /* RSA-PSS signature over header with key in NPDM. */
u32 magic;
u8 distribution; /* System vs gamecard. */
u8 content_type;
u8 crypto_type; /* Which keyblob (field 1) */
u8 kaek_ind; /* Which kaek index? */
u64 nca_size; /* Entire archive size. */
u64 title_id;
u8 _0x218[0x4]; /* Padding. */
union {
u32 sdk_version; /* What SDK was this built with? */
struct {
u8 sdk_revision;
u8 sdk_micro;
u8 sdk_minor;
u8 sdk_major;
};
};
u8 crypto_type2; /* Which keyblob (field 2) */
u8 fixed_key_generation;
u8 _0x222[0xE]; /* Padding. */
u8 rights_id[0x10]; /* Rights ID (for titlekey crypto). */
nca_section_entry_t section_entries[4]; /* Section entry metadata. */
u8 section_hashes[4][0x20]; /* SHA-256 hashes for each section header. */
u8 nca_keys[4][0x10]; /* Key area (encrypted) */
u8 _0x340[0xC0]; /* Padding. */
nca_fs_header_t fs_headers[4]; /* FS section headers. */
} PACKED nca_header_t;
typedef struct {
u32 magic;
u32 _0x4;
u32 _0x8;
u8 mmu_flags;
u8 _0xD;
u8 main_thread_prio;
u8 default_cpuid;
u64 _0x10;
u32 process_category;
u32 main_stack_size;
char title_name[0x50];
u32 aci0_offset;
u32 aci0_size;
u32 acid_offset;
u32 acid_size;
} PACKED npdm_t;
typedef struct {
u64 title_id;
u32 version;
u8 type;
u8 unk1;
u16 extended_header_size;
u16 content_cnt;
u16 content_meta_cnt;
u8 attr;
u8 unk2[0x03];
u32 required_dl_sysver;
u8 unk3[0x04];
} PACKED cnmt_header;
typedef struct {
u64 patch_tid; /* Patch TID / Application TID */
u32 min_sysver; /* Minimum system/application version */
u32 min_appver; /* Minimum application version (only for base applications) */
} PACKED cnmt_extended_header;
typedef struct {
u8 hash[0x20];
u8 nca_id[0x10];
u8 size[6];
u8 type;
u8 id_offset;
} PACKED cnmt_content_record;
typedef struct {
u8 type;
u64 title_id;
u32 version;
u32 required_dl_sysver;
u32 nca_cnt;
u8 digest[SHA256_HASH_SIZE];
char digest_str[(SHA256_HASH_SIZE * 2) + 1];
u8 min_keyblob;
u32 min_sysver;
u64 patch_tid;
u32 min_appver;
} cnmt_xml_program_info;
typedef struct {
u8 type;
u8 nca_id[SHA256_HASH_SIZE / 2];
char nca_id_str[SHA256_HASH_SIZE + 1];
u64 size;
u8 hash[SHA256_HASH_SIZE];
char hash_str[(SHA256_HASH_SIZE * 2) + 1];
u8 keyblob;
u8 id_offset;
u64 cnt_record_offset; // Relative to the start of the content records section in the CNMT
u8 decrypted_nca_keys[NCA_KEY_AREA_SIZE];
u8 encrypted_header_mod[NCA_FULL_HEADER_LENGTH];
} cnmt_xml_content_info;
typedef struct {
u32 nca_index;
u8 *hash_table;
u64 hash_table_offset; // Relative to NCA start
u64 hash_table_size;
u8 block_mod_cnt;
u8 *block_data[2];
u64 block_offset[2]; // Relative to NCA start
u64 block_size[2];
} nca_program_mod_data;
typedef struct {
u32 nca_index;
u64 xml_size;
char *xml_data;
u8 nacp_icon_cnt; // Only used with Control NCAs
nacp_icons_ctx *nacp_icons; // Only used with Control NCAs
} xml_record_info;
typedef struct {
u64 section_offset; // Relative to NCA start
u64 section_size;
u64 hash_table_offset; // Relative to NCA start
u64 hash_block_size;
u32 hash_block_cnt;
u64 pfs0_offset; // Relative to NCA start
u64 pfs0_size;
u64 title_cnmt_offset; // Relative to NCA start
u64 title_cnmt_size;
} nca_cnmt_mod_data;
typedef struct {
u32 sig_type;
u8 signature[0x100];
u8 padding[0x3C];
char sig_issuer[0x40];
u8 titlekey_block[0x100];
u8 unk1;
u8 titlekey_type;
u8 unk2[0x03];
u8 master_key_rev;
u8 unk3[0x0A];
u64 ticket_id;
u64 device_id;
u8 rights_id[0x10];
u32 account_id;
u8 unk4[0x0C];
} PACKED rsa2048_sha256_ticket;
typedef struct {
bool has_rights_id;
u8 rights_id[0x10];
char rights_id_str[33];
char tik_filename[37];
char cert_filename[38];
u8 enc_titlekey[0x10];
u8 dec_titlekey[0x10];
u8 cert_data[ETICKET_CERT_FILE_SIZE];
rsa2048_sha256_ticket tik_data;
bool retrieved_tik;
bool missing_tik;
} title_rights_ctx;
// Used in HFS0 / ExeFS / RomFS browsers
typedef struct {
u64 size;
char sizeStr[32];
} browser_entry_size_info;
typedef struct {
u8 type; // 1 = Dir, 2 = File
u64 offset; // Relative to directory/file table, depending on type
browser_entry_size_info sizeInfo; // Only used if type == 2
} romfs_browser_entry;
bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize);
bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title_rights_ctx *rights_info, u8 *decrypted_nca_keys, bool retrieveTitleKeyData);
bool retrieveTitleKeyFromGameCardTicket(title_rights_ctx *rights_info, u8 *decrypted_nca_keys);
bool processProgramNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data **output, u32 *cur_mod_cnt, u32 idx);
bool retrieveCnmtNcaData(NcmStorageId curStorageId, u8 *ncaBuf, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, u32 cnmtNcaIndex, nca_cnmt_mod_data *output, title_rights_ctx *rights_info);
bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *cnmt_mod);
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,206 +0,0 @@
#pragma once
#ifndef __UI_H__
#define __UI_H__
#define FB_WIDTH 1280
#define FB_HEIGHT 720
#define CHAR_PT_SIZE 12
#define SCREEN_DPI_CNT 96
#define LINE_HEIGHT (font_height + (font_height / 4))
#define LINE_STRING_OFFSET (font_height / 8)
#define STRING_DEFAULT_POS 8, 8
#define STRING_X_POS 8
#define STRING_Y_POS(x) (8 + ((x) * LINE_HEIGHT) + ((x) > 0 ? LINE_STRING_OFFSET : 0))
#define BG_COLOR_RGB 50, 50, 50
#define FONT_COLOR_RGB 255, 255, 255
#define HIGHLIGHT_BG_COLOR_RGB 33, 34, 39
#define HIGHLIGHT_FONT_COLOR_RGB 0, 255, 197
#define FONT_COLOR_SUCCESS_RGB 0, 255, 0
#define FONT_COLOR_ERROR_RGB 255, 0, 0
#define FONT_COLOR_TITLE_RGB 115, 115, 255
#define EMPTY_BAR_COLOR_RGB 0, 0, 0
#define COMMON_MAX_ELEMENTS 9
#define HFS0_MAX_ELEMENTS 14
#define ROMFS_MAX_ELEMENTS 12
#define SDCARD_MAX_ELEMENTS 3
#define ORPHAN_MAX_ELEMENTS 12
#define BATCH_MAX_ELEMENTS 14
#define OPTIONS_X_START_POS (35 * CHAR_PT_SIZE)
#define OPTIONS_X_END_POS (OPTIONS_X_START_POS + (6 * CHAR_PT_SIZE))
#define OPTIONS_X_END_POS_NSP (FB_WIDTH - (4 * CHAR_PT_SIZE))
#define TAB_WIDTH 4
#define BROWSER_ICON_DIMENSION 16
// UTF-8 sequences
#define UPWARDS_ARROW "\xE2\x86\x91"
#define DOWNWARDS_ARROW "\xE2\x86\x93"
#define NINTENDO_FONT_A "\xEE\x82\xA0"
#define NINTENDO_FONT_B "\xEE\x82\xA1"
#define NINTENDO_FONT_X "\xEE\x82\xA2"
#define NINTENDO_FONT_Y "\xEE\x82\xA3"
#define NINTENDO_FONT_L "\xEE\x82\xA4"
#define NINTENDO_FONT_R "\xEE\x82\xA5"
#define NINTENDO_FONT_ZL "\xEE\x82\xA6"
#define NINTENDO_FONT_ZR "\xEE\x82\xA7"
#define NINTENDO_FONT_DPAD "\xEE\x82\xAA"
#define NINTENDO_FONT_PLUS "\xEE\x82\xB5"
#define NINTENDO_FONT_HOME "\xEE\x82\xB9"
#define NINTENDO_FONT_LSTICK "\xEE\x83\x81"
#define NINTENDO_FONT_RSTICK "\xEE\x83\x82"
typedef enum {
resultNone,
resultShowMainMenu,
resultShowGameCardMenu,
resultShowXciDumpMenu,
resultDumpXci,
resultShowNspDumpMenu,
resultShowNspAppDumpMenu,
resultShowNspPatchDumpMenu,
resultShowNspAddOnDumpMenu,
resultDumpNsp,
resultShowHfs0Menu,
resultShowRawHfs0PartitionDumpMenu,
resultDumpRawHfs0Partition,
resultShowHfs0PartitionDataDumpMenu,
resultDumpHfs0PartitionData,
resultShowHfs0BrowserMenu,
resultHfs0BrowserGetList,
resultShowHfs0Browser,
resultHfs0BrowserCopyFile,
resultShowExeFsMenu,
resultShowExeFsSectionDataDumpMenu,
resultDumpExeFsSectionData,
resultShowExeFsSectionBrowserMenu,
resultExeFsSectionBrowserGetList,
resultShowExeFsSectionBrowser,
resultExeFsSectionBrowserCopyFile,
resultShowRomFsMenu,
resultShowRomFsSectionDataDumpMenu,
resultDumpRomFsSectionData,
resultShowRomFsSectionBrowserMenu,
resultRomFsSectionBrowserGetEntries,
resultShowRomFsSectionBrowser,
resultRomFsSectionBrowserChangeDir,
resultRomFsSectionBrowserCopyFile,
resultRomFsSectionBrowserCopyDir,
resultDumpGameCardCertificate,
resultShowSdCardEmmcMenu,
resultShowSdCardEmmcTitleMenu,
resultShowSdCardEmmcOrphanPatchAddOnMenu,
resultShowSdCardEmmcBatchModeMenu,
resultSdCardEmmcBatchDump,
resultShowTicketMenu,
resultDumpTicket,
resultShowUpdateMenu,
resultUpdateNSWDBXml,
resultUpdateApplication,
resultExit
} UIResult;
typedef enum {
stateMainMenu,
stateGameCardMenu,
stateXciDumpMenu,
stateDumpXci,
stateNspDumpMenu,
stateNspAppDumpMenu,
stateNspPatchDumpMenu,
stateNspAddOnDumpMenu,
stateDumpNsp,
stateHfs0Menu,
stateRawHfs0PartitionDumpMenu,
stateDumpRawHfs0Partition,
stateHfs0PartitionDataDumpMenu,
stateDumpHfs0PartitionData,
stateHfs0BrowserMenu,
stateHfs0BrowserGetList,
stateHfs0Browser,
stateHfs0BrowserCopyFile,
stateExeFsMenu,
stateExeFsSectionDataDumpMenu,
stateDumpExeFsSectionData,
stateExeFsSectionBrowserMenu,
stateExeFsSectionBrowserGetList,
stateExeFsSectionBrowser,
stateExeFsSectionBrowserCopyFile,
stateRomFsMenu,
stateRomFsSectionDataDumpMenu,
stateDumpRomFsSectionData,
stateRomFsSectionBrowserMenu,
stateRomFsSectionBrowserGetEntries,
stateRomFsSectionBrowser,
stateRomFsSectionBrowserChangeDir,
stateRomFsSectionBrowserCopyFile,
stateRomFsSectionBrowserCopyDir,
stateDumpGameCardCertificate,
stateSdCardEmmcMenu,
stateSdCardEmmcTitleMenu,
stateSdCardEmmcOrphanPatchAddOnMenu,
stateSdCardEmmcBatchModeMenu,
stateSdCardEmmcBatchDump,
stateTicketMenu,
stateDumpTicket,
stateUpdateMenu,
stateUpdateNSWDBXml,
stateUpdateApplication
} UIState;
typedef enum {
MENUTYPE_MAIN = 0,
MENUTYPE_GAMECARD,
MENUTYPE_SDCARD_EMMC
} curMenuType;
void uiFill(int x, int y, int width, int height, u8 r, u8 g, u8 b);
void uiDrawIcon(const u8 *icon, int width, int height, int x, int y);
bool uiLoadJpgFromMem(u8 *rawJpg, size_t rawJpgSize, int expectedWidth, int expectedHeight, int desiredWidth, int desiredHeight, u8 **outBuf);
bool uiLoadJpgFromFile(const char *filename, int expectedWidth, int expectedHeight, int desiredWidth, int desiredHeight, u8 **outBuf);
void uiDrawString(int x, int y, u8 r, u8 g, u8 b, const char *fmt, ...);
u32 uiGetStrWidth(const char *fmt, ...);
void uiRefreshDisplay();
void uiStatusMsg(const char *fmt, ...);
void uiUpdateStatusMsg();
void uiClearStatusMsg();
void uiPleaseWait(u8 wait);
void uiClearScreen();
void uiPrintHeadline();
bool uiInit();
void uiDeinit();
void uiSetState(UIState state);
UIState uiGetState();
UIResult uiProcess();
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,293 +0,0 @@
#pragma once
#ifndef __UTIL_H__
#define __UTIL_H__
#include <switch.h>
#include "nca.h"
#define HBLOADER_BASE_PATH "sdmc:/switch/"
#define APP_BASE_PATH HBLOADER_BASE_PATH APP_TITLE "/"
#define XCI_DUMP_PATH APP_BASE_PATH "XCI/"
#define NSP_DUMP_PATH APP_BASE_PATH "NSP/"
#define HFS0_DUMP_PATH APP_BASE_PATH "HFS0/"
#define EXEFS_DUMP_PATH APP_BASE_PATH "ExeFS/"
#define ROMFS_DUMP_PATH APP_BASE_PATH "RomFS/"
#define CERT_DUMP_PATH APP_BASE_PATH "Certificate/"
#define BATCH_OVERRIDES_PATH NSP_DUMP_PATH "BatchOverrides/"
#define TICKET_PATH APP_BASE_PATH "Ticket/"
#define CONFIG_PATH APP_BASE_PATH "config.bin"
#define NRO_NAME APP_TITLE ".nro"
#define NRO_PATH APP_BASE_PATH NRO_NAME
#define NSWDB_XML_PATH APP_BASE_PATH "NSWreleases.xml"
#define KEYS_FILE_PATH HBLOADER_BASE_PATH "prod.keys"
#define CFW_PATH_ATMOSPHERE "sdmc:/atmosphere/contents/"
#define CFW_PATH_SXOS "sdmc:/sxos/titles/"
#define CFW_PATH_REINX "sdmc:/ReiNX/titles/"
#define HTTP_USER_AGENT APP_TITLE "/" APP_VERSION " (Nintendo Switch)"
#define GITHUB_API_URL "https://api.github.com/repos/DarkMatterCore/nxdumptool/releases/latest"
#define GITHUB_API_JSON_RELEASE_NAME "name"
#define GITHUB_API_JSON_ASSETS "assets"
#define GITHUB_API_JSON_ASSETS_NAME "name"
#define GITHUB_API_JSON_ASSETS_DL_URL "browser_download_url"
#define NOINTRO_DOM_CHECK_URL "https://datomatic.no-intro.org/qchknsw.php"
#define NSWDB_XML_URL "http://nswdb.com/xml.php"
#define NSWDB_XML_ROOT "releases"
#define NSWDB_XML_CHILD "release"
#define NSWDB_XML_CHILD_TITLEID "titleid"
#define NSWDB_XML_CHILD_IMGCRC "imgcrc"
#define NSWDB_XML_CHILD_RELEASENAME "releasename"
#define LOCKPICK_RCM_URL "https://github.com/shchmue/Lockpick_RCM"
#define KiB (1024.0)
#define MiB (1024.0 * KiB)
#define GiB (1024.0 * MiB)
#define NAME_BUF_LEN 2048
#define DUMP_BUFFER_SIZE (u64)0x400000 // 4 MiB (4194304 bytes)
#define GAMECARD_READ_BUFFER_SIZE DUMP_BUFFER_SIZE // 4 MiB (4194304 bytes)
#define NCA_CTR_BUFFER_SIZE DUMP_BUFFER_SIZE // 4 MiB (4194304 bytes)
#define NSP_XML_BUFFER_SIZE (u64)0xA00000 // 10 MiB (10485760 bytes)
#define APPLICATION_PATCH_BITMASK (u64)0x800
#define APPLICATION_ADDON_BITMASK (u64)0xFFFFFFFFFFFF0000
#define NACP_APPNAME_LEN 0x200
#define NACP_AUTHOR_LEN 0x100
#define VERSION_STR_LEN 0x40
#define MEDIA_UNIT_SIZE 0x200
#define ISTORAGE_PARTITION_CNT 2
#define GAMECARD_WAIT_TIME 3 // 3 seconds
#define GAMECARD_HEADER_MAGIC (u32)0x48454144 // "HEAD"
#define GAMECARD_SIZE_1GiB (u64)0x40000000
#define GAMECARD_SIZE_2GiB (u64)0x80000000
#define GAMECARD_SIZE_4GiB (u64)0x100000000
#define GAMECARD_SIZE_8GiB (u64)0x200000000
#define GAMECARD_SIZE_16GiB (u64)0x400000000
#define GAMECARD_SIZE_32GiB (u64)0x800000000
#define GAMECARD_UPDATE_TITLEID (u64)0x0100000000000816
#define GAMECARD_ECC_BLOCK_SIZE (u64)0x200 // 512 bytes
#define GAMECARD_ECC_DATA_SIZE (u64)0x24 // 36 bytes
#define GAMECARD_TYPE1_PARTITION_CNT 3 // "update" (0), "normal" (1), "secure" (2)
#define GAMECARD_TYPE2_PARTITION_CNT 4 // "update" (0), "logo" (1), "normal" (2), "secure" (3)
#define GAMECARD_TYPE(x) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? "Type 0x01" : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? "Type 0x02" : "Unknown"))
#define GAMECARD_TYPE1_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Normal" : ((x) == 2 ? "Secure" : "Unknown")))
#define GAMECARD_TYPE2_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Logo" : ((x) == 2 ? "Normal" : ((x) == 3 ? "Secure" : "Unknown"))))
#define GAMECARD_PARTITION_NAME(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? GAMECARD_TYPE1_PART_NAMES(y) : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? GAMECARD_TYPE2_PART_NAMES(y) : "Unknown"))
#define HFS0_MAGIC (u32)0x48465330 // "HFS0"
#define HFS0_TO_ISTORAGE_IDX(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? ((y) < (GAMECARD_TYPE1_PARTITION_CNT - 1) ? 0 : 1) : ((y) < (GAMECARD_TYPE2_PARTITION_CNT - 1) ? 0 : 1))
#define NACP_ICON_SQUARE_DIMENSION 256
#define NACP_ICON_DOWNSCALED 96
#define round_up(x, y) ((x) + (((y) - ((x) % (y))) % (y))) // Aligns 'x' bytes to a 'y' bytes boundary
#define ORPHAN_ENTRY_TYPE_PATCH 1
#define ORPHAN_ENTRY_TYPE_ADDON 2
#define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0]))) // Returns the max number of elements that can be stored in an array
#define MAX_CHARACTERS(x) (MAX_ELEMENTS((x)) - 1) // Returns the max number of characters that can be stored in char array while also leaving space for a NULL terminator
#define BIS_MOUNT_NAME "sys:"
#define BIS_CERT_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e0"
#define BIS_COMMON_TIK_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e1"
#define BIS_PERSONALIZED_TIK_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e2"
#define SMOOTHING_FACTOR (double)0.1
#define CANCEL_BTN_SEC_HOLD 2 // The cancel button must be held for at least CANCEL_BTN_SEC_HOLD seconds to cancel an ongoing operation
typedef struct {
u64 titleId;
u32 version;
u32 ncmIndex;
NcmStorageId storageId;
char name[NACP_APPNAME_LEN];
char fixedName[NACP_APPNAME_LEN];
char author[NACP_AUTHOR_LEN];
char versionStr[VERSION_STR_LEN];
u8 *icon;
u64 contentSize;
char contentSizeStr[32];
} base_app_ctx_t;
typedef struct {
u64 titleId;
u32 version;
u32 ncmIndex;
NcmStorageId storageId;
char versionStr[VERSION_STR_LEN];
u64 contentSize;
char contentSizeStr[32];
} patch_addon_ctx_t;
typedef struct {
u32 index;
u8 type; // 1 = Patch, 2 = AddOn
char name[NACP_APPNAME_LEN];
char fixedName[NACP_APPNAME_LEN];
char orphanListStr[NACP_APPNAME_LEN * 2];
} orphan_patch_addon_entry;
typedef struct {
int line_offset;
u64 totalSize;
char totalSizeStr[32];
u64 curOffset;
char curOffsetStr[32];
u64 seqDumpCurOffset;
u8 progress;
u64 start;
u64 now;
u64 remainingTime;
char etaInfo[32];
double lastSpeed;
double averageSpeed;
u32 cancelBtnState;
u32 cancelBtnStatePrev;
u64 cancelStartTmr;
u64 cancelEndTmr;
} progress_ctx_t;
typedef enum {
ROMFS_TYPE_APP = 0,
ROMFS_TYPE_PATCH,
ROMFS_TYPE_ADDON
} selectedRomFsType;
typedef enum {
TICKET_TYPE_APP = 0,
TICKET_TYPE_PATCH,
TICKET_TYPE_ADDON
} selectedTicketType;
typedef struct {
bool isFat32;
bool setXciArchiveBit;
bool keepCert;
bool trimDump;
bool calcCrc;
bool useNoIntroLookup;
bool useBrackets;
} PACKED xciOptions;
typedef struct {
bool isFat32;
bool useNoIntroLookup;
bool removeConsoleData;
bool tiklessDump;
bool npdmAcidRsaPatch;
bool dumpDeltaFragments;
bool useBrackets;
} PACKED nspOptions;
typedef enum {
BATCH_SOURCE_ALL = 0,
BATCH_SOURCE_SDCARD,
BATCH_SOURCE_EMMC,
BATCH_SOURCE_CNT
} batchModeSourceStorage;
typedef struct {
bool dumpAppTitles;
bool dumpPatchTitles;
bool dumpAddOnTitles;
bool isFat32;
bool removeConsoleData;
bool tiklessDump;
bool npdmAcidRsaPatch;
bool dumpDeltaFragments;
bool skipDumpedTitles;
bool rememberDumpedTitles;
bool haltOnErrors;
bool useBrackets;
batchModeSourceStorage batchModeSrc;
} PACKED batchOptions;
typedef struct {
bool removeConsoleData;
} PACKED ticketOptions;
typedef struct {
bool isFat32;
bool useLayeredFSDir;
} PACKED ncaFsOptions;
typedef struct {
xciOptions xciDumpCfg;
nspOptions nspDumpCfg;
batchOptions batchDumpCfg;
ticketOptions tikDumpCfg;
ncaFsOptions exeFsDumpCfg;
ncaFsOptions romFsDumpCfg;
} PACKED dumpOptions;
void loadConfig();
void saveConfig();
void freeFilenameBuffer(void);
void freeRomFsBrowserEntries();
void freeHfs0ExeFsEntriesSizes();
bool initApplicationResources(int argc, char **argv);
void deinitApplicationResources();
void appletModeOperationWarning();
void formatETAString(u64 curTime, char *out, size_t outSize);
void generateSdCardEmmcTitleList();
void truncateBrowserEntryName(char *str);
bool getHfs0FileList(u32 partition);
bool readNcaExeFsSection(u32 titleIndex, bool usePatch);
int readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, int desiredIdOffset);
bool getExeFsFileList();
bool getRomFsFileList(u32 dir_offset, bool usePatch);
void printProgressBar(progress_ctx_t *progressCtx, bool calcData, u64 chunkSize);
void setProgressBarError(progress_ctx_t *progressCtx);
bool cancelProcessCheck(progress_ctx_t *progressCtx);
bool yesNoPrompt(const char *message);
bool checkIfDumpedXciContainsCertificate(const char *xciPath);
bool checkIfDumpedNspContainsConsoleData(const char *nspPath);
void removeDirectoryWithVerbose(const char *path, const char *msg);
void gameCardDumpNSWDBCheck(u32 crc);
void noIntroDumpCheck(bool isDigital, u32 crc);
#endif

View file

@ -1,16 +1,16 @@
{
"__comment__": "Comments about how specific fields work use keys that follow the '__{field}_comment__' format. These don't have to be replicated in your translation files.",
"unknown": "Unknown",
"applet_mode_warning": "\uE8B2 Warning: the application is running under Applet Mode! \uE8B2\nThis mode severely limits the amount of usable RAM. If you consistently reproduce any crashes, please consider running the application via title override (hold R while launching a game).",
"time_format": "12",
"__time_format_comment__": "Use 12 for a 12-hour clock, or 24 for a 24-hour clock",
"date": "{1:02d}/{2:02d}/{0} {3:02d}:{4:02d}:{5:02d} {6}",
"__date_comment__": "{0} = Year, {1} = Month, {2} = Day, {3} = Hour, {4} = Minute, {5} = Second, {6} = AM/PM (if time_format is set to 12)",
"libnx_abort": "Fatal error triggered in libnx!\nError code: 0x{:08X}.",
"exception_triggered": "Fatal exception triggered!\nReason: {} (0x{:X})."
}

View file

@ -1,25 +1,25 @@
{
"dump_options_info": "Dump options are displayed in their respective menus for convenience.",
"overclock": {
"label": "Overclock",
"description": "Overclocks both CPU and MEM to 1785 MHz and 1600 MHz, respectively, in order to speed up dump operations. This is considered a relatively safe action.\n\nIf the application is running under title override mode, and sys-clk is active, and a clock profile has been created for the overridden title, this setting has no effect at all.",
"value_enabled": "Yes",
"value_disabled": "No"
},
"naming_convention": {
"label": "Naming convention",
"description": "Sets the naming convention used for all output dumps.\n\n\uE016 Full: \"{Name} [{Id}][v{Version}][{Type}]\".\n\uE016 ID and version only: \"{Id}_v{Version}_{Type}\".\n\nIf \"Full\" is selected, the display version string will also be appended to dumped updates whenever possible.",
"value_00": "Full",
"value_01": "ID and version only"
},
"update_nswdb_xml": {
"label": "Update NSWDB XML",
"description": "Retrieves the latest NSWDB XML, which can be optionally used to validate checksums from gamecard dumps. Requires Internet connectivity."
},
"update_app": {
"label": "Update application",
"description": "Checks if an update is available in nxdumptool's GitHub repository. Requires Internet connectivity.",
@ -30,12 +30,12 @@
"update_action": "Update"
}
},
"update_dialog": {
"cancel": "Cancel",
"close": "Close"
},
"notifications": {
"no_internet_connection": "Internet connection unavailable. Unable to update.",
"update_failed": "Update failed! Check the logfile for more info.",

View file

@ -34,7 +34,7 @@ namespace nxdt::views
/* Set custom spacing. */
this->setSpacing(this->getSpacing() / 2);
this->setMarginBottom(20);
/* Logo. */
this->logo = new brls::Image();
this->logo->setImage(BOREALIS_ASSET("icon/" APP_TITLE ".jpg"));
@ -42,15 +42,15 @@ namespace nxdt::views
this->logo->setHeight(LOGO_SIZE);
this->logo->setScaleType(brls::ImageScaleType::NO_RESIZE);
this->logo->setOpacity(0.3F);
/* Description. */
this->addView(new AboutTabLabel(brls::LabelStyle::CRASH, "about_tab/description"_i18n, true));
/* Copyright. */
brls::Label *copyright = new brls::Label(brls::LabelStyle::DESCRIPTION, i18n::getStr("about_tab/copyright"_i18n, APP_AUTHOR, GITHUB_REPOSITORY_URL), true);
copyright->setHorizontalAlign(NVG_ALIGN_CENTER);
this->addView(copyright);
/* Dependencies. */
this->addView(new brls::Header("about_tab/dependencies/header"_i18n));
this->addView(new brls::Label(brls::LabelStyle::SMALL, i18n::getStr("about_tab/dependencies/line_00"_i18n, APP_TITLE, BOREALIS_URL), true));
@ -58,28 +58,28 @@ namespace nxdt::views
this->addView(new AboutTabLabel(brls::LabelStyle::SMALL, i18n::getStr("about_tab/dependencies/line_02"_i18n, FATFS_URL)));
this->addView(new AboutTabLabel(brls::LabelStyle::SMALL, i18n::getStr("about_tab/dependencies/line_03"_i18n, LZ4_URL)));
this->addView(new AboutTabLabel(brls::LabelStyle::SMALL, i18n::getStr("about_tab/dependencies/line_04"_i18n, JSON_C_URL)));
/* Acknowledgments. */
this->addView(new brls::Header("about_tab/acknowledgments/header"_i18n));
for(int i = 0; i < 8; i++) this->addView(new AboutTabLabel(brls::LabelStyle::SMALL, i18n::getStr(fmt::format("about_tab/acknowledgments/line_{:02d}", i)), false));
for(int i = 8; i < 11; i++) this->addView(new brls::Label(brls::LabelStyle::SMALL, i18n::getStr(fmt::format("about_tab/acknowledgments/line_{:02d}", i)), true));
/* Additional links and resources. */
this->addView(new brls::Header("about_tab/links/header"_i18n));
this->addView(new AboutTabLabel(brls::LabelStyle::SMALL, i18n::getStr("about_tab/links/line_00"_i18n, DISCORD_SERVER_URL)));
}
AboutTab::~AboutTab(void)
{
delete this->logo;
}
void AboutTab::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
{
this->logo->frame(ctx);
brls::ScrollView::draw(vg, x, y, width, height, style, ctx);
}
void AboutTab::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
{
this->logo->setBoundaries(
@ -87,9 +87,9 @@ namespace nxdt::views
this->y + (this->height - this->logo->getHeight()) / 2,
this->logo->getWidth(),
this->logo->getHeight());
this->logo->invalidate();
brls::ScrollView::layout(vg, style, stash);
}
}

View file

@ -28,13 +28,13 @@ size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src,
LOG_MSG("Invalid parameters!");
return 0;
}
size_t i, crypt_res = 0;
u64 cur_sector = sector;
u8 *dst_u8 = (u8*)dst;
const u8 *src_u8 = (const u8*)src;
for(i = 0; i < size; i += sector_size, cur_sector++)
{
/* We have to force a sector reset on each new sector to actually enable Nintendo AES-XTS cipher tweak. */
@ -42,6 +42,6 @@ size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src,
crypt_res = (encrypt ? aes128XtsEncrypt(ctx, dst_u8 + i, src_u8 + i, sector_size) : aes128XtsDecrypt(ctx, dst_u8 + i, src_u8 + i, sector_size));
if (crypt_res != sector_size) break;
}
return i;
}

View file

@ -61,15 +61,15 @@ bool bfttfInitialize(void)
NcaContext *nca_ctx = NULL;
RomFileSystemContext romfs_ctx = {0};
bool ret = false;
SCOPED_LOCK(&g_bfttfMutex)
{
ret = g_bfttfInterfaceInit;
if (ret) break;
u32 count = 0;
u64 prev_title_id = 0;
/* Allocate memory for a temporary NCA context. */
nca_ctx = calloc(1, sizeof(NcaContext));
if (!nca_ctx)
@ -77,14 +77,14 @@ bool bfttfInitialize(void)
LOG_MSG("Failed to allocate memory for temporary NCA context!");
break;
}
/* Retrieve BFTTF data. */
for(u32 i = 0; i < g_fontInfoCount; i++)
{
BfttfFontInfo *font_info = &(g_fontInfo[i]);
TitleInfo *title_info = NULL;
RomFileSystemFileEntry *romfs_file_entry = NULL;
/* Check if the title ID for the current font container matches the one from the previous font container. */
/* We won't have to reinitialize both NCA and RomFS contexts if that's the case. */
if (font_info->title_id != prev_title_id)
@ -95,22 +95,22 @@ bool bfttfInitialize(void)
LOG_MSG("Failed to get title info for %016lX!", font_info->title_id);
continue;
}
/* Initialize NCA context. */
/* NCA contexts don't need to be freed beforehand. */
bool nca_ctx_init = ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), \
title_info->version.value, NULL);
/* Free title info. */
titleFreeTitleInfo(&title_info);
/* Check if NCA context initialization succeeded. */
if (!nca_ctx_init)
{
LOG_MSG("Failed to initialize Data NCA context for %016lX!", font_info->title_id);
continue;
}
/* Initialize RomFS context. */
/* This will also free a previous RomFS context, if available. */
if (!romfsInitializeContext(&romfs_ctx, &(nca_ctx->fs_ctx[0]), NULL))
@ -118,32 +118,32 @@ bool bfttfInitialize(void)
LOG_MSG("Failed to initialize RomFS context for Data NCA from %016lX!", font_info->title_id);
continue;
}
/* Update previous title ID. */
prev_title_id = font_info->title_id;
}
/* Get RomFS file entry. */
if (!(romfs_file_entry = romfsGetFileEntryByPath(&romfs_ctx, font_info->path)))
{
LOG_MSG("Failed to retrieve RomFS file entry in %016lX!", font_info->title_id);
continue;
}
/* Check file size. */
if (!romfs_file_entry->size)
{
LOG_MSG("File size for \"%s\" in %016lX is zero!", font_info->path, font_info->title_id);
continue;
}
/* Allocate memory for BFTTF data. */
if (!(font_info->data = malloc(romfs_file_entry->size)))
{
LOG_MSG("Failed to allocate 0x%lX bytes for \"%s\" in %016lX!", romfs_file_entry->size, font_info->path, font_info->title_id);
continue;
}
/* Read BFTFF data. */
if (!romfsReadFileEntryData(&romfs_ctx, romfs_file_entry, font_info->data, romfs_file_entry->size, 0))
{
@ -152,10 +152,10 @@ bool bfttfInitialize(void)
font_info->data = NULL;
continue;
}
/* Update BFTTF size. */
font_info->size = (u32)romfs_file_entry->size;
/* Decode BFTTF data. */
if (!bfttfDecodeFont(font_info))
{
@ -165,20 +165,20 @@ bool bfttfInitialize(void)
font_info->size = 0;
continue;
}
/* Increase retrieved BFTTF count. */
count++;
}
/* Update flags. */
ret = g_bfttfInterfaceInit = (count > 0);
if (!ret) LOG_MSG("No BFTTF fonts retrieved!");
}
romfsFreeContext(&romfs_ctx);
if (nca_ctx) free(nca_ctx);
return ret;
}
@ -190,16 +190,16 @@ void bfttfExit(void)
for(u32 i = 0; i < g_fontInfoCount; i++)
{
BfttfFontInfo *font_info = &(g_fontInfo[i]);
font_info->size = 0;
if (font_info->data)
{
free(font_info->data);
font_info->data = NULL;
}
}
g_bfttfInterfaceInit = false;
}
}
@ -211,9 +211,9 @@ bool bfttfGetFontByType(BfttfFontData *font_data, u8 font_type)
LOG_MSG("Invalid parameters!");
return false;
}
bool ret = false;
SCOPED_LOCK(&g_bfttfMutex)
{
BfttfFontInfo *font_info = &(g_fontInfo[font_type]);
@ -222,14 +222,14 @@ bool bfttfGetFontByType(BfttfFontData *font_data, u8 font_type)
LOG_MSG("BFTTF font data unavailable for type 0x%02X!", font_type);
break;
}
font_data->type = font_type;
font_data->size = (font_info->size - 8);
font_data->address = (font_info->data + 8);
ret = true;
}
return ret;
}
@ -240,12 +240,12 @@ static bool bfttfDecodeFont(BfttfFontInfo *font_info)
LOG_MSG("Invalid parameters!");
return false;
}
for(u32 i = 8; i < font_info->size; i += 4)
{
u32 *ptr = (u32*)(font_info->data + i);
*ptr = (*ptr ^ g_bfttfKey);
}
return true;
}

File diff suppressed because it is too large Load diff

View file

@ -56,16 +56,16 @@ bool certRetrieveCertificateByName(Certificate *dst, const char *name)
LOG_MSG("Invalid parameters!");
return false;
}
bool ret = false;
SCOPED_LOCK(&g_esCertSaveMutex)
{
if (!certOpenEsCertSaveFile()) break;
ret = _certRetrieveCertificateByName(dst, name);
certCloseEsCertSaveFile();
}
return ret;
}
@ -76,16 +76,16 @@ bool certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const
LOG_MSG("Invalid parameters!");
return false;
}
bool ret = false;
SCOPED_LOCK(&g_esCertSaveMutex)
{
if (!certOpenEsCertSaveFile()) break;
ret = _certRetrieveCertificateChainBySignatureIssuer(dst, issuer);
certCloseEsCertSaveFile();
}
return ret;
}
@ -96,32 +96,32 @@ u8 *certGenerateRawCertificateChainBySignatureIssuer(const char *issuer, u64 *ou
LOG_MSG("Invalid parameters!");
return NULL;
}
CertificateChain chain = {0};
u8 *raw_chain = NULL;
u64 raw_chain_size = 0;
if (!certRetrieveCertificateChainBySignatureIssuer(&chain, issuer))
{
LOG_MSG("Error retrieving certificate chain for \"%s\"!", issuer);
return NULL;
}
raw_chain_size = certCalculateRawCertificateChainSize(&chain);
raw_chain = malloc(raw_chain_size);
if (!raw_chain)
{
LOG_MSG("Unable to allocate memory for raw \"%s\" certificate chain! (0x%lX).", issuer, raw_chain_size);
goto end;
}
certCopyCertificateChainDataToMemoryBuffer(raw_chain, &chain);
*out_size = raw_chain_size;
end:
certFreeCertificateChain(&chain);
return raw_chain;
}
@ -132,64 +132,64 @@ u8 *certRetrieveRawCertificateChainFromGameCardByRightsId(const FsRightsId *id,
LOG_MSG("Invalid parameters!");
return NULL;
}
char raw_chain_filename[0x30] = {0};
u64 raw_chain_offset = 0, raw_chain_size = 0;
u8 *raw_chain = NULL;
bool success = false;
utilsGenerateHexStringFromData(raw_chain_filename, sizeof(raw_chain_filename), id->c, sizeof(id->c), false);
strcat(raw_chain_filename, ".cert");
if (!gamecardGetHashFileSystemEntryInfoByName(GameCardHashFileSystemPartitionType_Secure, raw_chain_filename, &raw_chain_offset, &raw_chain_size))
{
LOG_MSG("Error retrieving offset and size for \"%s\" entry in secure hash FS partition!", raw_chain_filename);
return NULL;
}
if (raw_chain_size < SIGNED_CERT_MIN_SIZE)
{
LOG_MSG("Invalid size for \"%s\"! (0x%lX).", raw_chain_filename, raw_chain_size);
return NULL;
}
raw_chain = malloc(raw_chain_size);
if (!raw_chain)
{
LOG_MSG("Unable to allocate memory for raw \"%s\" certificate chain! (0x%lX).", raw_chain_filename, raw_chain_size);
return NULL;
}
if (!gamecardReadStorage(raw_chain, raw_chain_size, raw_chain_offset))
{
LOG_MSG("Failed to read \"%s\" data from the inserted gamecard!", raw_chain_filename);
goto end;
}
*out_size = raw_chain_size;
success = true;
end:
if (!success && raw_chain)
{
free(raw_chain);
raw_chain = NULL;
}
return raw_chain;
}
static bool certOpenEsCertSaveFile(void)
{
if (g_esCertSaveCtx) return true;
g_esCertSaveCtx = save_open_savefile(CERT_SAVEFILE_PATH, 0);
if (!g_esCertSaveCtx)
{
LOG_MSG("Failed to open ES certificate system savefile!");
return false;
}
return true;
}
@ -207,41 +207,41 @@ static bool _certRetrieveCertificateByName(Certificate *dst, const char *name)
LOG_MSG("ES certificate savefile not opened!");
return false;
}
u64 cert_size = 0;
char cert_path[SAVE_FS_LIST_MAX_NAME_LENGTH] = {0};
allocation_table_storage_ctx_t fat_storage = {0};
snprintf(cert_path, SAVE_FS_LIST_MAX_NAME_LENGTH, CERT_SAVEFILE_STORAGE_BASE_PATH "%s", name);
if (!save_get_fat_storage_from_file_entry_by_path(g_esCertSaveCtx, cert_path, &fat_storage, &cert_size))
{
LOG_MSG("Failed to locate certificate \"%s\" in ES certificate system save!", name);
return false;
}
if (cert_size < SIGNED_CERT_MIN_SIZE || cert_size > SIGNED_CERT_MAX_SIZE)
{
LOG_MSG("Invalid size for certificate \"%s\"! (0x%lX).", name, cert_size);
return false;
}
dst->size = cert_size;
u64 br = save_allocation_table_storage_read(&fat_storage, dst->data, 0, dst->size);
if (br != dst->size)
{
LOG_MSG("Failed to read 0x%lX bytes from certificate \"%s\"! Read 0x%lX bytes.", dst->size, name, br);
return false;
}
dst->type = certGetCertificateType(dst->data, dst->size);
if (dst->type == CertType_None)
{
LOG_MSG("Invalid certificate type for \"%s\"!", name);
return false;
}
return true;
}
@ -251,22 +251,22 @@ static u8 certGetCertificateType(void *data, u64 data_size)
u32 sig_type = 0, pub_key_type = 0;
u64 signed_cert_size = 0;
u8 type = CertType_None;
if (!data || data_size < SIGNED_CERT_MIN_SIZE || data_size > SIGNED_CERT_MAX_SIZE)
{
LOG_MSG("Invalid parameters!");
return type;
}
if (!(cert_common_block = certGetCommonBlock(data)) || !(signed_cert_size = certGetSignedCertificateSize(data)) || signed_cert_size > data_size)
{
LOG_MSG("Input buffer doesn't hold a valid signed certificate!");
return type;
}
sig_type = signatureGetSigType(data, true);
pub_key_type = __builtin_bswap32(cert_common_block->pub_key_type);
switch(sig_type)
{
case SignatureType_Rsa4096Sha1:
@ -287,7 +287,7 @@ static u8 certGetCertificateType(void *data, u64 data_size)
default:
break;
}
return type;
}
@ -298,29 +298,29 @@ static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst
LOG_MSG("ES certificate savefile not opened!");
return false;
}
u32 i = 0;
char issuer_copy[0x40] = {0}, *pch = NULL, *state = NULL;
bool success = true;
dst->count = certGetCertificateCountInSignatureIssuer(issuer);
if (!dst->count)
{
LOG_MSG("Invalid signature issuer string!");
return false;
}
dst->certs = calloc(dst->count, sizeof(Certificate));
if (!dst->certs)
{
LOG_MSG("Unable to allocate memory for the certificate chain! (0x%lX).", dst->count * sizeof(Certificate));
return false;
}
/* Copy string to avoid problems with strtok_r(). */
/* The "Root-" parent from the issuer string is skipped. */
snprintf(issuer_copy, sizeof(issuer_copy), "%s", issuer + 5);
pch = strtok_r(issuer_copy, "-", &state);
while(pch)
{
@ -330,41 +330,41 @@ static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst
success = false;
break;
}
i++;
pch = strtok_r(NULL, "-", &state);
}
if (!success) certFreeCertificateChain(dst);
return success;
}
static u32 certGetCertificateCountInSignatureIssuer(const char *issuer)
{
if (!issuer || !*issuer) return 0;
u32 count = 0;
char issuer_copy[0x40] = {0}, *pch = NULL, *state = NULL;
/* Copy string to avoid problems with strtok_r(). */
/* The "Root-" parent from the issuer string is skipped. */
snprintf(issuer_copy, sizeof(issuer_copy), "%s", issuer + 5);
pch = strtok_r(issuer_copy, "-", &state);
while(pch)
{
count++;
pch = strtok_r(NULL, "-", &state);
}
return count;
}
static u64 certCalculateRawCertificateChainSize(const CertificateChain *chain)
{
if (!chain || !chain->count || !chain->certs) return 0;
u64 chain_size = 0;
for(u32 i = 0; i < chain->count; i++) chain_size += chain->certs[i].size;
return chain_size;
@ -373,7 +373,7 @@ static u64 certCalculateRawCertificateChainSize(const CertificateChain *chain)
static void certCopyCertificateChainDataToMemoryBuffer(void *dst, const CertificateChain *chain)
{
if (!chain || !chain->count || !chain->certs) return;
u8 *dst_u8 = (u8*)dst;
for(u32 i = 0; i < chain->count; i++)
{

View file

@ -52,64 +52,64 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
LOG_MSG("Invalid parameters!");
return false;
}
u32 i = 0, pfs_entry_count = 0;
size_t cnmt_filename_len = 0;
u8 content_meta_type = 0;
u64 title_id = 0, cur_offset = 0;
bool success = false, invalid_ext_header_size = false, invalid_ext_data_size = false, dump_packaged_header = false;
/* Free output context beforehand. */
cnmtFreeContext(out);
/* Initialize Partition FS context. */
if (!pfsInitializeContext(&(out->pfs_ctx), &(nca_ctx->fs_ctx[0])))
{
LOG_MSG("Failed to initialize Partition FS context!");
goto end;
}
/* Get Partition FS entry count. Edge case, we should never trigger this. */
if (!(pfs_entry_count = pfsGetEntryCount(&(out->pfs_ctx))))
{
LOG_MSG("Partition FS has no file entries!");
goto end;
}
/* Look for the '.cnmt' file entry index. */
for(i = 0; i < pfs_entry_count; i++)
{
if ((out->cnmt_filename = pfsGetEntryNameByIndex(&(out->pfs_ctx), i)) && (cnmt_filename_len = strlen(out->cnmt_filename)) >= CNMT_MINIMUM_FILENAME_LENGTH && \
!strcasecmp(out->cnmt_filename + cnmt_filename_len - 5, ".cnmt")) break;
}
if (i >= pfs_entry_count)
{
LOG_MSG("'.cnmt' entry unavailable in Partition FS!");
goto end;
}
//LOG_MSG("Found '.cnmt' entry \"%s\" in Meta NCA \"%s\".", out->cnmt_filename, nca_ctx->content_id_str);
/* Retrieve content meta type and title ID from the '.cnmt' filename. */
if (!cnmtGetContentMetaTypeAndTitleIdFromFileName(out->cnmt_filename, cnmt_filename_len, &content_meta_type, &title_id)) goto end;
/* Get '.cnmt' file entry. */
if (!(out->pfs_entry = pfsGetEntryByIndex(&(out->pfs_ctx), i)))
{
LOG_MSG("Failed to get '.cnmt' entry from Partition FS!");
goto end;
}
/* Check raw CNMT size. */
if (!out->pfs_entry->size)
{
LOG_DATA(out->pfs_entry, sizeof(PartitionFileSystemEntry), "Invalid raw CNMT size! Partition FS entry dump:");
goto end;
}
/* Allocate memory for the raw CNMT data. */
out->raw_data_size = out->pfs_entry->size;
if (!(out->raw_data = malloc(out->raw_data_size)))
@ -117,42 +117,42 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
LOG_MSG("Failed to allocate memory for the raw CNMT data!");
goto end;
}
/* Read raw CNMT data into memory buffer. */
if (!pfsReadEntryData(&(out->pfs_ctx), out->pfs_entry, out->raw_data, out->raw_data_size, 0))
{
LOG_MSG("Failed to read raw CNMT data!");
goto end;
}
/* Calculate SHA-256 checksum for the whole raw CNMT. */
sha256CalculateHash(out->raw_data_hash, out->raw_data, out->raw_data_size);
/* Verify packaged header. */
out->packaged_header = (ContentMetaPackagedContentMetaHeader*)out->raw_data;
cur_offset += sizeof(ContentMetaPackagedContentMetaHeader);
if (out->packaged_header->title_id != title_id)
{
LOG_MSG("CNMT title ID mismatch! (%016lX != %016lX).", out->packaged_header->title_id, title_id);
dump_packaged_header = true;
goto end;
}
if (out->packaged_header->content_meta_type != content_meta_type)
{
LOG_MSG("CNMT content meta type mismatch! (0x%02X != 0x%02X).", out->packaged_header->content_meta_type, content_meta_type);
dump_packaged_header = true;
goto end;
}
if (!out->packaged_header->content_count && out->packaged_header->content_meta_type != NcmContentMetaType_SystemUpdate)
{
LOG_MSG("Invalid content count!");
dump_packaged_header = true;
goto end;
}
if ((out->packaged_header->content_meta_type == NcmContentMetaType_SystemUpdate && !out->packaged_header->content_meta_count) || \
(out->packaged_header->content_meta_type != NcmContentMetaType_SystemUpdate && out->packaged_header->content_meta_count))
{
@ -160,13 +160,13 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
dump_packaged_header = true;
goto end;
}
/* Save pointer to extended header. */
if (out->packaged_header->extended_header_size)
{
out->extended_header = (out->raw_data + cur_offset);
cur_offset += out->packaged_header->extended_header_size;
switch(out->packaged_header->content_meta_type)
{
case NcmContentMetaType_SystemUpdate:
@ -194,14 +194,14 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
invalid_ext_header_size = (out->packaged_header->extended_header_size > 0);
break;
}
if (invalid_ext_header_size)
{
LOG_MSG("Invalid extended header size!");
dump_packaged_header = true;
goto end;
}
if (invalid_ext_data_size)
{
LOG_DATA(out->extended_header, out->packaged_header->extended_header_size, "Invalid extended data size! CNMT Extended Header dump:");
@ -209,56 +209,56 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
goto end;
}
}
/* Save pointer to packaged content infos. */
if (out->packaged_header->content_count)
{
out->packaged_content_info = (NcmPackagedContentInfo*)(out->raw_data + cur_offset);
cur_offset += (out->packaged_header->content_count * sizeof(NcmPackagedContentInfo));
}
/* Save pointer to content meta infos. */
if (out->packaged_header->content_meta_count)
{
out->content_meta_info = (NcmContentMetaInfo*)(out->raw_data + cur_offset);
cur_offset += (out->packaged_header->content_meta_count * sizeof(NcmContentMetaInfo));
}
/* Save pointer to the extended data block. */
if (out->extended_data_size)
{
out->extended_data = (out->raw_data + cur_offset);
cur_offset += out->extended_data_size;
}
/* Save pointer to digest. */
out->digest = (out->raw_data + cur_offset);
cur_offset += CNMT_DIGEST_SIZE;
/* Safety check: verify raw CNMT size. */
if (cur_offset != out->raw_data_size)
{
LOG_MSG("Raw CNMT size mismatch! (0x%lX != 0x%lX).", cur_offset, out->raw_data_size);
goto end;
}
/* Update output context. */
out->nca_ctx = nca_ctx;
/* Update content type context info in NCA context. */
nca_ctx->content_type_ctx = out;
nca_ctx->content_type_ctx_patch = false;
success = true;
end:
if (!success)
{
if (dump_packaged_header) LOG_DATA(out->packaged_header, sizeof(ContentMetaPackagedContentMetaHeader), "CNMT Packaged Header dump:");
cnmtFreeContext(out);
}
return success;
}
@ -269,20 +269,20 @@ bool cnmtUpdateContentInfo(ContentMetaContext *cnmt_ctx, NcaContext *nca_ctx)
LOG_MSG("Invalid parameters!");
return false;
}
/* Return right away if we're dealing with a Meta NCA. */
if (nca_ctx->content_type == NcmContentType_Meta) return true;
bool success = false;
for(u16 i = 0; i < cnmt_ctx->packaged_header->content_count; i++)
{
NcmPackagedContentInfo *packaged_content_info = &(cnmt_ctx->packaged_content_info[i]);
NcmContentInfo *content_info = &(packaged_content_info->info);
u64 content_size = 0;
titleConvertNcmContentSizeToU64(content_info->size, &content_size);
if (content_size == nca_ctx->content_size && content_info->content_type == nca_ctx->content_type && content_info->id_offset == nca_ctx->id_offset)
{
/* Jackpot. Copy content ID and hash to our raw CNMT. */
@ -294,10 +294,10 @@ bool cnmtUpdateContentInfo(ContentMetaContext *cnmt_ctx, NcaContext *nca_ctx)
break;
}
}
if (!success) LOG_MSG("Unable to find CNMT content info entry for \"%s\" NCA! (Title ID %016lX, size 0x%lX, type 0x%02X, ID offset 0x%02X).", nca_ctx->content_id_str, \
cnmt_ctx->packaged_header->title_id, nca_ctx->content_size, nca_ctx->content_type, nca_ctx->id_offset);
return success;
}
@ -308,7 +308,7 @@ bool cnmtGenerateNcaPatch(ContentMetaContext *cnmt_ctx)
LOG_MSG("Invalid parameters!");
return false;
}
/* Check if we really need to generate this patch. */
u8 cnmt_hash[SHA256_HASH_SIZE] = {0};
sha256CalculateHash(cnmt_hash, cnmt_ctx->raw_data, cnmt_ctx->raw_data_size);
@ -317,17 +317,17 @@ bool cnmtGenerateNcaPatch(ContentMetaContext *cnmt_ctx)
LOG_MSG("Skipping CNMT patching - no content records have been changed.");
return true;
}
/* Generate Partition FS entry patch. */
if (!pfsGenerateEntryPatch(&(cnmt_ctx->pfs_ctx), cnmt_ctx->pfs_entry, cnmt_ctx->raw_data, cnmt_ctx->raw_data_size, 0, &(cnmt_ctx->nca_patch)))
{
LOG_MSG("Failed to generate Partition FS entry patch!");
return false;
}
/* Update NCA content type context patch status. */
cnmt_ctx->nca_ctx->content_type_ctx_patch = true;
return true;
}
@ -335,13 +335,13 @@ void cnmtWriteNcaPatch(ContentMetaContext *cnmt_ctx, void *buf, u64 buf_size, u6
{
NcaContext *nca_ctx = NULL;
NcaHierarchicalSha256Patch *nca_patch = (cnmt_ctx ? &(cnmt_ctx->nca_patch) : NULL);
/* Using cnmtIsValidContext() here would probably take up precious CPU cycles. */
if (!nca_patch || nca_patch->written || !(nca_ctx = cnmt_ctx->nca_ctx) || nca_ctx->content_type != NcmContentType_Meta || !nca_ctx->content_type_ctx_patch) return;
/* Attempt to write Partition FS entry patch. */
pfsWriteEntryPatchToMemoryBuffer(&(cnmt_ctx->pfs_ctx), nca_patch, buf, buf_size, buf_offset);
/* Check if we need to update the NCA content type context patch status. */
if (nca_patch->written)
{
@ -357,19 +357,19 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_
LOG_MSG("Invalid parameters!");
return false;
}
u32 i, j;
char *xml_buf = NULL;
u64 xml_buf_size = 0;
char digest_str[0x41] = {0};
u8 count = 0;
bool success = false, invalid_nca = false;
/* Free AuthoringTool-like XML data if needed. */
if (cnmt_ctx->authoring_tool_xml) free(cnmt_ctx->authoring_tool_xml);
cnmt_ctx->authoring_tool_xml = NULL;
cnmt_ctx->authoring_tool_xml_size = 0;
if (!CNMT_ADD_FMT_STR("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" \
"<ContentMeta>\n" \
" <Type>%s</Type>\n" \
@ -382,7 +382,7 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_
cnmt_ctx->packaged_header->version.value, \
cnmt_ctx->packaged_header->version.application_version.release_ver, \
cnmt_ctx->packaged_header->version.application_version.private_ver)) goto end;
/* ContentMetaAttribute. */
for(i = 0; i < ContentMetaAttribute_Count; i++)
{
@ -390,17 +390,17 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_
if (!CNMT_ADD_FMT_STR(" <ContentMetaAttribute>%s</ContentMetaAttribute>\n", g_cnmtAttributeStrings[i])) goto end;
count++;
}
if (!count && !CNMT_ADD_FMT_STR(" <ContentMetaAttribute />\n")) goto end;
/* RequiredDownloadSystemVersion. */
if (!CNMT_ADD_FMT_STR(" <RequiredDownloadSystemVersion>%u</RequiredDownloadSystemVersion>\n", cnmt_ctx->packaged_header->required_download_system_version.value)) goto end;
/* Contents. */
for(i = 0; i < nca_ctx_count; i++)
{
NcaContext *cur_nca_ctx = &(nca_ctx[i]);
/* Check if this NCA is really referenced by our CNMT. */
if (cur_nca_ctx->content_type != NcmContentType_Meta)
{
@ -409,19 +409,19 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_
{
if (!memcmp(cnmt_ctx->packaged_content_info[j].info.content_id.c, cur_nca_ctx->content_id.c, 0x10)) break;
}
invalid_nca = (j >= cnmt_ctx->packaged_header->content_count);
} else {
/* Meta NCAs: quick and dirty pointer comparison because why not. */
invalid_nca = (cnmt_ctx->nca_ctx != cur_nca_ctx);
}
if (invalid_nca)
{
LOG_MSG("NCA \"%s\" isn't referenced by this CNMT!", cur_nca_ctx->content_id_str);
goto end;
}
if (!CNMT_ADD_FMT_STR(" <Content>\n" \
" <Type>%s</Type>\n" \
" <Id>%s</Id>\n" \
@ -437,9 +437,9 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_
cur_nca_ctx->key_generation, \
cur_nca_ctx->id_offset)) goto end;
}
utilsGenerateHexStringFromData(digest_str, sizeof(digest_str), cnmt_ctx->digest, CNMT_DIGEST_SIZE, false);
/* ContentMeta, Digest, KeyGenerationMin, KeepGeneration and KeepGenerationSpecified. */
if (!CNMT_ADD_FMT_STR(" <ContentMeta />\n" \
" <Digest>%s</Digest>\n" \
@ -448,7 +448,7 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_
" <KeepGenerationSpecified />\n", \
digest_str, \
cnmt_ctx->nca_ctx->key_generation)) goto end;
/* RequiredSystemVersion (Application, Patch) / RequiredApplicationVersion (AddOnContent). */
/* PatchId (Application) / ApplicationId (Patch, AddOnContent). */
if (cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Application || cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Patch || \
@ -456,36 +456,36 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_
{
u32 required_title_version = cnmtGetRequiredTitleVersion(cnmt_ctx);
const char *required_title_version_str = cnmtGetRequiredTitleVersionString(cnmt_ctx->packaged_header->content_meta_type);
u64 required_title_id = cnmtGetRequiredTitleId(cnmt_ctx);
const char *required_title_type_str = cnmtGetRequiredTitleTypeString(cnmt_ctx->packaged_header->content_meta_type);
if (!CNMT_ADD_FMT_STR(" <%s>%u</%s>\n" \
" <%s>0x%016lx</%s>\n", \
required_title_version_str, required_title_version, required_title_version_str, \
required_title_type_str, required_title_id, required_title_type_str)) goto end;
}
/* RequiredApplicationVersion (Application). */
if (cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Application)
{
if (!CNMT_ADD_FMT_STR(" <RequiredApplicationVersion>%u</RequiredApplicationVersion>\n", \
((ContentMetaApplicationMetaExtendedHeader*)cnmt_ctx->extended_header)->required_application_version.value)) goto end;
}
if (!(success = CNMT_ADD_FMT_STR("</ContentMeta>"))) goto end;
/* Update CNMT context. */
cnmt_ctx->authoring_tool_xml = xml_buf;
cnmt_ctx->authoring_tool_xml_size = strlen(xml_buf);
end:
if (!success)
{
if (xml_buf) free(xml_buf);
LOG_MSG("Failed to generate CNMT AuthoringTool XML!");
}
return success;
}
@ -496,20 +496,20 @@ static bool cnmtGetContentMetaTypeAndTitleIdFromFileName(const char *cnmt_filena
LOG_MSG("Invalid parameters!");
return false;
}
u8 i = 0;
const char *pch1 = NULL, *pch2 = NULL;
size_t content_meta_type_str_len = 0;
pch1 = (const char*)strstr(cnmt_filename, "_");
pch2 = (cnmt_filename + cnmt_filename_len - 5);
if (!pch1 || !(content_meta_type_str_len = (pch1 - cnmt_filename)) || (pch2 - ++pch1) != 16)
{
LOG_MSG("Invalid '.cnmt' filename in Partition FS! (\"%s\").", cnmt_filename);
return false;
}
for(i = NcmContentMetaType_SystemProgram; i <= NcmContentMetaType_Delta; i++)
{
/* Dirty loop hack, but whatever. */
@ -518,33 +518,33 @@ static bool cnmtGetContentMetaTypeAndTitleIdFromFileName(const char *cnmt_filena
i = (NcmContentMetaType_Application - 1);
continue;
}
if (!strncasecmp(cnmt_filename, titleGetNcmContentMetaTypeName(i), content_meta_type_str_len))
{
*out_content_meta_type = i;
break;
}
}
if (i > NcmContentMetaType_Delta)
{
LOG_MSG("Invalid content meta type \"%.*s\" in '.cnmt' filename! (\"%s\").", (int)content_meta_type_str_len, cnmt_filename, cnmt_filename);
return false;
}
if (!(*out_title_id = strtoull(pch1, NULL, 16)))
{
LOG_MSG("Invalid title ID in '.cnmt' filename! (\"%s\").", cnmt_filename);
return false;
}
return true;
}
static const char *cnmtGetRequiredTitleVersionString(u8 content_meta_type)
{
const char *str = NULL;
switch(content_meta_type)
{
case NcmContentMetaType_Application:
@ -558,14 +558,14 @@ static const char *cnmtGetRequiredTitleVersionString(u8 content_meta_type)
str = "Unknown";
break;
}
return str;
}
static const char *cnmtGetRequiredTitleTypeString(u8 content_meta_type)
{
const char *str = NULL;
switch(content_meta_type)
{
case NcmContentMetaType_Application:
@ -579,6 +579,6 @@ static const char *cnmtGetRequiredTitleTypeString(u8 content_meta_type)
str = "Unknown";
break;
}
return str;
}

View file

@ -77,23 +77,23 @@ static bool configValidateJsonNcaFsObject(const struct json_object *obj);
bool configInitialize(void)
{
bool ret = false;
SCOPED_LOCK(&g_configMutex)
{
ret = g_configInterfaceInit;
if (ret) break;
/* Parse JSON config. */
if (!configParseConfigJson())
{
LOG_MSG("Failed to parse JSON configuration!");
break;
}
/* Update flags. */
ret = g_configInterfaceInit = true;
}
return ret;
}
@ -104,7 +104,7 @@ void configExit(void)
/* Free JSON object. */
/* We don't need to write it back to the SD card - setter functions do that on their own. */
configFreeConfigJson();
/* Update flag. */
g_configInterfaceInit = false;
}
@ -119,7 +119,7 @@ CONFIG_SETTER(Integer, int);
static bool configParseConfigJson(void)
{
bool use_default_config = true, ret = false;
/* Read config JSON. */
g_configJson = json_object_from_file(CONFIG_PATH);
if (!g_configJson)
@ -127,19 +127,19 @@ static bool configParseConfigJson(void)
jsonLogLastError();
goto end;
}
/* Validate configuration. */
ret = configValidateJsonRootObject(g_configJson);
use_default_config = !ret;
end:
if (use_default_config)
{
LOG_MSG("Loading default configuration.");
/* Free config JSON. */
configFreeConfigJson();
/* Read default config JSON. */
g_configJson = json_object_from_file(DEFAULT_CONFIG_PATH);
if (g_configJson)
@ -150,7 +150,7 @@ end:
jsonLogLastError();
}
}
return ret;
}
@ -171,9 +171,9 @@ static bool configValidateJsonRootObject(const struct json_object *obj)
{
bool ret = false, overclock_found = false, naming_convention_found = false, dump_destination_found = false, gamecard_found = false;
bool nsp_found = false, ticket_found = false, nca_fs_found = false;
if (!jsonValidateObject(obj)) goto end;
json_object_object_foreach(obj, key, val)
{
CONFIG_VALIDATE_FIELD(Boolean, overclock);
@ -185,9 +185,9 @@ static bool configValidateJsonRootObject(const struct json_object *obj)
CONFIG_VALIDATE_OBJECT(NcaFs, nca_fs);
goto end;
}
ret = (overclock_found && naming_convention_found && dump_destination_found && gamecard_found && nsp_found && ticket_found && nca_fs_found);
end:
return ret;
}
@ -195,9 +195,9 @@ end:
static bool configValidateJsonGameCardObject(const struct json_object *obj)
{
bool ret = false, append_key_area_found = false, keep_certificate_found = false, trim_dump_found = false, calculate_checksum_found = false, checksum_lookup_method_found = false;
if (!jsonValidateObject(obj)) goto end;
json_object_object_foreach(obj, key, val)
{
CONFIG_VALIDATE_FIELD(Boolean, append_key_area);
@ -207,9 +207,9 @@ static bool configValidateJsonGameCardObject(const struct json_object *obj)
CONFIG_VALIDATE_FIELD(Integer, checksum_lookup_method, ConfigChecksumLookupMethod_None, ConfigChecksumLookupMethod_Count - 1);
goto end;
}
ret = (append_key_area_found && keep_certificate_found && trim_dump_found && calculate_checksum_found && checksum_lookup_method_found);
end:
return ret;
}
@ -218,9 +218,9 @@ static bool configValidateJsonNspObject(const struct json_object *obj)
{
bool ret = false, set_download_distribution_found = false, remove_console_data_found = false, remove_titlekey_crypto_found = false;
bool disable_linked_account_requirement_found = false, enable_screenshots_found = false, enable_video_capture_found = false, disable_hdcp_found = false, append_authoringtool_data_found = false, lookup_checksum_found = false;
if (!jsonValidateObject(obj)) goto end;
json_object_object_foreach(obj, key, val)
{
CONFIG_VALIDATE_FIELD(Boolean, set_download_distribution);
@ -234,10 +234,10 @@ static bool configValidateJsonNspObject(const struct json_object *obj)
CONFIG_VALIDATE_FIELD(Boolean, append_authoringtool_data);
goto end;
}
ret = (set_download_distribution_found && remove_console_data_found && remove_titlekey_crypto_found && disable_linked_account_requirement_found && \
enable_screenshots_found && enable_video_capture_found && disable_hdcp_found && append_authoringtool_data_found && lookup_checksum_found);
end:
return ret;
}
@ -245,17 +245,17 @@ end:
static bool configValidateJsonTicketObject(const struct json_object *obj)
{
bool ret = false, remove_console_data_found = false;
if (!jsonValidateObject(obj)) goto end;
json_object_object_foreach(obj, key, val)
{
CONFIG_VALIDATE_FIELD(Boolean, remove_console_data);
goto end;
}
ret = remove_console_data_found;
end:
return ret;
}
@ -263,17 +263,17 @@ end:
static bool configValidateJsonNcaFsObject(const struct json_object *obj)
{
bool ret = false, use_layeredfs_dir_found = false;
if (!jsonValidateObject(obj)) goto end;
json_object_object_foreach(obj, key, val)
{
CONFIG_VALIDATE_FIELD(Boolean, use_layeredfs_dir);
goto end;
}
ret = use_layeredfs_dir_found;
end:
return ret;
}

View file

@ -33,10 +33,10 @@ Result esCountCommonTicket(s32 *out_count)
struct {
s32 num_tickets;
} out;
Result rc = serviceDispatchOut(&g_esSrv, 9, out);
if (R_SUCCEEDED(rc) && out_count) *out_count = out.num_tickets;
return rc;
}
@ -45,10 +45,10 @@ Result esCountPersonalizedTicket(s32 *out_count)
struct {
s32 num_tickets;
} out;
Result rc = serviceDispatchOut(&g_esSrv, 10, out);
if (R_SUCCEEDED(rc) && out_count) *out_count = out.num_tickets;
return rc;
}
@ -57,14 +57,14 @@ Result esListCommonTicket(s32 *out_entries_written, FsRightsId *out_ids, s32 cou
struct {
s32 num_rights_ids_written;
} out;
Result rc = serviceDispatchInOut(&g_esSrv, 11, *out_entries_written, out,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
.buffers = { { out_ids, (size_t)count * sizeof(FsRightsId) } }
);
if (R_SUCCEEDED(rc) && out_entries_written) *out_entries_written = out.num_rights_ids_written;
return rc;
}
@ -73,14 +73,14 @@ Result esListPersonalizedTicket(s32 *out_entries_written, FsRightsId *out_ids, s
struct {
s32 num_rights_ids_written;
} out;
Result rc = serviceDispatchInOut(&g_esSrv, 12, *out_entries_written, out,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
.buffers = { { out_ids, (size_t)count * sizeof(FsRightsId) } }
);
if (R_SUCCEEDED(rc) && out_entries_written) *out_entries_written = out.num_rights_ids_written;
return rc;
}

View file

@ -29,7 +29,7 @@ Result fsOpenGameCardStorage(FsStorage *out, const FsGameCardHandle *handle, u32
FsGameCardHandle handle;
u32 partition;
} in = { *handle, partition };
return serviceDispatchIn(fsGetServiceSession(), 30, in,
.out_num_objects = 1,
.out_objects = &out->s
@ -50,17 +50,17 @@ Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator *d, const FsGameCard
struct {
FsGameCardHandle handle;
} in = { *handle };
struct {
u32 title_version;
u64 title_id;
} out;
Result rc = serviceDispatchInOut(&d->s, 203, in, out);
if (R_SUCCEEDED(rc) && out_title_version) *out_title_version = out.title_version;
if (R_SUCCEEDED(rc) && out_title_id) *out_title_id = out.title_id;
return rc;
}
@ -70,12 +70,12 @@ Result fsDeviceOperatorGetGameCardDeviceCertificate(FsDeviceOperator *d, const F
FsGameCardHandle handle;
u64 buf_size;
} in = { *handle, sizeof(FsGameCardCertificate) };
Result rc = serviceDispatchIn(&d->s, 206, in,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
.buffers = { { out, sizeof(FsGameCardCertificate) } }
);
return rc;
}
@ -84,11 +84,11 @@ Result fsDeviceOperatorGetGameCardIdSet(FsDeviceOperator *d, FsGameCardIdSet *ou
struct {
u64 buf_size;
} in = { sizeof(FsGameCardIdSet) };
Result rc = serviceDispatchIn(&d->s, 208, in,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
.buffers = { { out, sizeof(FsGameCardIdSet) } }
);
return rc;
}

File diff suppressed because it is too large Load diff

View file

@ -29,14 +29,14 @@ bool hfsReadPartitionData(HashFileSystemContext *ctx, void *out, u64 read_size,
LOG_MSG("Invalid parameters!");
return false;
}
/* Read partition data. */
if (!gamecardReadStorage(out, read_size, ctx->offset + offset))
{
LOG_MSG("Failed to read Hash FS partition data!");
return false;
}
return true;
}
@ -47,14 +47,14 @@ bool hfsReadEntryData(HashFileSystemContext *ctx, HashFileSystemEntry *fs_entry,
LOG_MSG("Invalid parameters!");
return false;
}
/* Read entry data. */
if (!hfsReadPartitionData(ctx, out, read_size, ctx->header_size + fs_entry->offset + offset))
{
LOG_MSG("Failed to read Partition FS entry data!");
return false;
}
return true;
}
@ -63,13 +63,13 @@ bool hfsGetTotalDataSize(HashFileSystemContext *ctx, u64 *out_size)
u64 total_size = 0;
u32 entry_count = hfsGetEntryCount(ctx);
HashFileSystemEntry *fs_entry = NULL;
if (!entry_count || !out_size)
{
LOG_MSG("Invalid parameters!");
return false;
}
for(u32 i = 0; i < entry_count; i++)
{
if (!(fs_entry = hfsGetEntryByIndex(ctx, i)))
@ -77,12 +77,12 @@ bool hfsGetTotalDataSize(HashFileSystemContext *ctx, u64 *out_size)
LOG_MSG("Failed to retrieve Hash FS entry #%u!", i);
return false;
}
total_size += fs_entry->size;
}
*out_size = total_size;
return true;
}
@ -91,15 +91,15 @@ bool hfsGetEntryIndexByName(HashFileSystemContext *ctx, const char *name, u32 *o
HashFileSystemEntry *fs_entry = NULL;
u32 entry_count = hfsGetEntryCount(ctx), name_table_size = 0;
char *name_table = hfsGetNameTable(ctx);
if (!entry_count || !name_table || !name || !*name || !out_idx)
{
LOG_MSG("Invalid parameters!");
return false;
}
name_table_size = ((HashFileSystemHeader*)ctx->header)->name_table_size;
for(u32 i = 0; i < entry_count; i++)
{
if (!(fs_entry = hfsGetEntryByIndex(ctx, i)))
@ -107,19 +107,19 @@ bool hfsGetEntryIndexByName(HashFileSystemContext *ctx, const char *name, u32 *o
LOG_MSG("Failed to retrieve Hash FS entry #%u!", i);
break;
}
if (fs_entry->name_offset >= name_table_size)
{
LOG_MSG("Name offset from Hash FS entry #%u exceeds name table size!", i);
break;
}
if (!strcmp(name_table + fs_entry->name_offset, name))
{
*out_idx = i;
return true;
}
}
return false;
}

View file

@ -30,12 +30,12 @@ static bool g_httpInterfaceInit = false;
bool httpInitialize(void)
{
bool ret = false;
SCOPED_LOCK(&g_httpMutex)
{
ret = g_httpInterfaceInit;
if (ret) break;
/* Initialize CURL. */
CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
if (res != CURLE_OK)
@ -43,11 +43,11 @@ bool httpInitialize(void)
LOG_MSG("%s", curl_easy_strerror(res));
break;
}
/* Update flags. */
ret = g_httpInterfaceInit = true;
}
return ret;
}
@ -57,7 +57,7 @@ void httpExit(void)
{
/* Cleanup CURL. */
curl_global_cleanup();
/* Update flag. */
g_httpInterfaceInit = false;
}
@ -73,25 +73,25 @@ size_t httpWriteBufferCallback(char *buffer, size_t size, size_t nitems, void *o
{
size_t total_size = (size * nitems);
HttpBuffer *http_buffer = (HttpBuffer*)outstream;
if (!total_size) return 0;
char *data_tmp = realloc(http_buffer->data, http_buffer->size + total_size);
if (!data_tmp) return 0;
http_buffer->data = data_tmp;
data_tmp = NULL;
memcpy(http_buffer->data + http_buffer->size, buffer, total_size);
http_buffer->size += total_size;
return total_size;
}
bool httpPerformGetRequest(const char *url, bool force_https, size_t *outsize, HttpWriteCallback write_cb, void *write_ptr, HttpProgressCallback progress_cb, void *progress_ptr)
{
bool ret = false;
SCOPED_LOCK(&g_httpMutex)
{
if (!g_httpInterfaceInit || !url || !*url)
@ -99,14 +99,14 @@ bool httpPerformGetRequest(const char *url, bool force_https, size_t *outsize, H
LOG_MSG("Invalid parameters!");
break;
}
CURL *curl = NULL;
CURLcode res = CURLE_OK;
long http_code = 0;
curl_off_t download_size = 0, content_length = 0;
char curl_err_buf[CURL_ERROR_SIZE] = {0};
const char *error_str = NULL;
/* Start CURL session. */
curl = curl_easy_init();
if (!curl)
@ -114,7 +114,7 @@ bool httpPerformGetRequest(const char *url, bool force_https, size_t *outsize, H
LOG_MSG("Failed to start CURL session for \"%s\"!", url);
break;
}
/* Set CURL options. */
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_USERAGENT, HTTP_USER_AGENT);
@ -129,29 +129,29 @@ bool httpPerformGetRequest(const char *url, bool force_https, size_t *outsize, H
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, HTTP_LOW_SPEED_TIME);
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, HTTP_BUFFER_SIZE);
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)(force_https ? CURL_HTTP_VERSION_2TLS : CURL_HTTP_VERSION_1_1));
if (write_cb) curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
if (write_ptr) curl_easy_setopt(curl, CURLOPT_WRITEDATA, write_ptr);
if (progress_cb)
{
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_cb);
}
if (progress_ptr) curl_easy_setopt(curl, CURLOPT_XFERINFODATA, progress_ptr);
/* Perform GET request. */
res = curl_easy_perform(curl);
/* Get HTTP request properties. */
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &download_size);
curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &content_length);
/* End CURL session. */
curl_easy_cleanup(curl);
/* Update return value. */
ret = (res == CURLE_OK && http_code >= 200 && http_code <= 299 && (content_length <= 0 || download_size == content_length));
if (ret)
@ -166,20 +166,20 @@ bool httpPerformGetRequest(const char *url, bool force_https, size_t *outsize, H
if (*curl_err_buf)
{
size_t curl_err_buf_len = strlen(curl_err_buf);
if (curl_err_buf[curl_err_buf_len - 1] == '\n') curl_err_buf[--curl_err_buf_len] = '\0';
if (curl_err_buf[curl_err_buf_len - 1] == '\r') curl_err_buf[--curl_err_buf_len] = '\0';
error_str = curl_err_buf;
} else {
error_str = curl_easy_strerror(res);
}
if (error_str) LOG_MSG("CURL error info: \"%s\".", error_str);
}
}
}
return ret;
}
@ -190,10 +190,10 @@ bool httpDownloadFile(const char *path, const char *url, bool force_https, HttpP
LOG_MSG("Invalid parameters!");
return false;
}
FILE *fd = NULL;
bool ret = false;
/* Open output file. */
fd = fopen(path, "wb");
if (!fd)
@ -201,19 +201,19 @@ bool httpDownloadFile(const char *path, const char *url, bool force_https, HttpP
LOG_MSG("Failed to open \"%s\" for writing!", path);
return false;
}
/* Perform HTTP GET request. */
ret = httpPerformGetRequest(url, force_https, NULL, httpWriteFileCallback, fd, progress_cb, progress_ptr);
/* Close output file. */
fclose(fd);
/* Delete output file if the request failed. */
if (!ret) remove(path);
/* Commit SD card filesystem changes. */
utilsCommitSdCardFileSystemChanges();
return ret;
}
@ -224,19 +224,19 @@ char *httpDownloadData(size_t *outsize, const char *url, bool force_https, HttpP
LOG_MSG("Invalid parameters!");
return NULL;
}
HttpBuffer http_buffer = {0};
bool ret = false;
/* Perform HTTP GET request. */
ret = httpPerformGetRequest(url, force_https, outsize, httpWriteBufferCallback, &http_buffer, progress_cb, progress_ptr);
/* Free output buffer if the request failed. */
if (!ret && http_buffer.data)
{
free(http_buffer.data);
http_buffer.data = NULL;
}
return http_buffer.data;
}

View file

@ -53,20 +53,20 @@ typedef struct {
u8 nca_header_key_source[AES_128_KEY_SIZE * 2]; ///< Retrieved from the .data segment in the FS sysmodule.
u8 nca_header_kek_sealed[AES_128_KEY_SIZE]; ///< Generated from nca_header_kek_source. Sealed by the SMC AES engine.
u8 nca_header_key[AES_128_KEY_SIZE * 2]; ///< Generated from nca_header_kek_sealed and nca_header_key_source.
///< RSA-2048-PSS moduli used to verify the main signature from NCA headers.
u8 nca_main_signature_moduli_prod[NcaSignatureKeyGeneration_Max][RSA2048_PUBKEY_SIZE]; ///< Moduli used in retail units. Retrieved from the .rodata segment in the FS sysmodule.
u8 nca_main_signature_moduli_dev[NcaSignatureKeyGeneration_Max][RSA2048_PUBKEY_SIZE]; ///< Moduli used in development units. Retrieved from the .rodata segment in the FS sysmodule.
///< AES-128-ECB keys needed to handle key area crypto from NCA headers.
u8 nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Count][AES_128_KEY_SIZE]; ///< Retrieved from the .rodata segment in the FS sysmodule.
u8 nca_kaek_sealed[NcaKeyAreaEncryptionKeyIndex_Count][NcaKeyGeneration_Max][AES_128_KEY_SIZE]; ///< Generated from nca_kaek_sources. Sealed by the SMC AES engine.
u8 nca_kaek[NcaKeyAreaEncryptionKeyIndex_Count][NcaKeyGeneration_Max][AES_128_KEY_SIZE]; ///< Unsealed key area encryption keys. Retrieved from the Lockpick_RCM keys file.
///< AES-128-CTR key needed to decrypt the console-specific eTicket RSA device key stored in PRODINFO.
u8 eticket_rsa_kek[AES_128_KEY_SIZE]; ///< eTicket RSA key encryption key (generic). Retrieved from the Lockpick_RCM keys file.
u8 eticket_rsa_kek_personalized[AES_128_KEY_SIZE]; ///< eTicket RSA key encryption key (console-specific). Retrieved from the Lockpick_RCM keys file.
///< AES-128-ECB keys needed to decrypt titlekeys.
u8 ticket_common_keys[NcaKeyGeneration_Max][AES_128_KEY_SIZE]; ///< Retrieved from the Lockpick_RCM keys file.
} KeysNcaKeyset;
@ -76,7 +76,7 @@ typedef struct {
const u8 gc_cardinfo_kek_source[AES_128_KEY_SIZE]; ///< Randomly generated KEK source to decrypt official CardInfo area keys.
const u8 gc_cardinfo_key_prod_source[AES_128_KEY_SIZE]; ///< CardInfo area key used in retail units. Obfuscated using the above KEK source and SMC AES engine keydata.
const u8 gc_cardinfo_key_dev_source[AES_128_KEY_SIZE]; ///< CardInfo area key used in development units. Obfuscated using the above KEK source and SMC AES engine keydata.
u8 gc_cardinfo_kek_sealed[AES_128_KEY_SIZE]; ///< Generated from gc_cardinfo_kek_source. Sealed by the SMC AES engine.
u8 gc_cardinfo_key_prod[AES_128_KEY_SIZE]; ///< Generated from gc_cardinfo_kek_sealed and gc_cardinfo_key_prod_source.
u8 gc_cardinfo_key_dev[AES_128_KEY_SIZE]; ///< Generated from gc_cardinfo_kek_sealed and gc_cardinfo_key_dev_source.
@ -254,72 +254,72 @@ static KeysMemoryInfo g_fsDataMemoryInfo = {
bool keysLoadKeyset(void)
{
bool ret = false;
SCOPED_LOCK(&g_keysetMutex)
{
ret = g_keysetLoaded;
if (ret) break;
/* Retrieve FS .rodata keys. */
if (!keysRetrieveKeysFromProgramMemory(&g_fsRodataMemoryInfo))
{
LOG_MSG("Unable to retrieve keys from FS .rodata segment!");
break;
}
/* Retrieve FS .data keys. */
if (!keysRetrieveKeysFromProgramMemory(&g_fsDataMemoryInfo))
{
LOG_MSG("Unable to retrieve keys from FS .data segment!");
break;
}
/* Derive NCA header key. */
if (!keysDeriveNcaHeaderKey())
{
LOG_MSG("Unable to derive NCA header key!");
break;
}
/* Derive sealed NCA KAEKs. */
if (!keysDeriveSealedNcaKeyAreaEncryptionKeys())
{
LOG_MSG("Unable to derive sealed NCA KAEKs!");
break;
}
/* Read additional keys from the keys file. */
if (!keysReadKeysFromFile()) break;
/* Get decrypted eTicket RSA device key. */
if (!keysGetDecryptedEticketRsaDeviceKey()) break;
/* Derive gamecard keys. */
if (!keysDeriveGameCardKeys()) break;
/* Update flags. */
ret = g_keysetLoaded = true;
}
/*if (ret)
{
LOG_DATA(&g_ncaKeyset, sizeof(KeysNcaKeyset), "NCA keyset dump:");
LOG_DATA(&g_eTicketRsaDeviceKey, sizeof(SetCalRsa2048DeviceKey), "eTicket RSA device key dump:");
LOG_DATA(&g_gameCardKeyset, sizeof(KeysGameCardKeyset), "Gamecard keyset dump:");
}*/
return ret;
}
const u8 *keysGetNcaHeaderKey(void)
{
const u8 *ret = NULL;
SCOPED_LOCK(&g_keysetMutex)
{
if (g_keysetLoaded) ret = (const u8*)(g_ncaKeyset.nca_header_key);
}
return ret;
}
@ -330,23 +330,23 @@ const u8 *keysGetNcaMainSignatureModulus(u8 key_generation)
LOG_MSG("Unsupported key generation value! (0x%02X).", key_generation);
return NULL;
}
bool dev_unit = utilsIsDevelopmentUnit();
const u8 *ret = NULL, null_modulus[RSA2048_PUBKEY_SIZE] = {0};
SCOPED_LOCK(&g_keysetMutex)
{
if (!g_keysetLoaded) break;
ret = (const u8*)(dev_unit ? g_ncaKeyset.nca_main_signature_moduli_dev[key_generation] : g_ncaKeyset.nca_main_signature_moduli_prod[key_generation]);
if (!memcmp(ret, null_modulus, RSA2048_PUBKEY_SIZE))
{
LOG_MSG("%s NCA header main signature modulus 0x%02X unavailable.", dev_unit ? "Development" : "Retail", key_generation);
ret = NULL;
}
}
return ret;
}
@ -354,32 +354,32 @@ bool keysDecryptNcaKeyAreaEntry(u8 kaek_index, u8 key_generation, void *dst, con
{
bool ret = false;
u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation);
if (kaek_index >= NcaKeyAreaEncryptionKeyIndex_Count)
{
LOG_MSG("Invalid KAEK index! (0x%02X).", kaek_index);
goto end;
}
if (key_gen_val >= NcaKeyGeneration_Max)
{
LOG_MSG("Invalid key generation value! (0x%02X).", key_gen_val);
goto end;
}
if (!dst || !src)
{
LOG_MSG("Invalid destination/source pointer.");
goto end;
}
SCOPED_LOCK(&g_keysetMutex)
{
if (!g_keysetLoaded) break;
Result rc = splCryptoGenerateAesKey(g_ncaKeyset.nca_kaek_sealed[kaek_index][key_gen_val], src, dst);
if (!(ret = R_SUCCEEDED(rc))) LOG_MSG("splCryptoGenerateAesKey failed! (0x%08X).", rc);
}
end:
return ret;
}
@ -388,24 +388,24 @@ const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation)
{
const u8 *ret = NULL;
u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation);
if (kaek_index >= NcaKeyAreaEncryptionKeyIndex_Count)
{
LOG_MSG("Invalid KAEK index! (0x%02X).", kaek_index);
goto end;
}
if (key_gen_val >= NcaKeyGeneration_Max)
{
LOG_MSG("Invalid key generation value! (0x%02X).", key_gen_val);
goto end;
}
SCOPED_LOCK(&g_keysetMutex)
{
if (g_keysetLoaded) ret = (const u8*)(g_ncaKeyset.nca_kaek[kaek_index][key_gen_val]);
}
end:
return ret;
}
@ -417,19 +417,19 @@ bool keysDecryptRsaOaepWrappedTitleKey(const void *rsa_wrapped_titlekey, void *o
LOG_MSG("Invalid parameters!");
return false;
}
bool ret = false;
SCOPED_LOCK(&g_keysetMutex)
{
if (!g_keysetLoaded) break;
size_t out_keydata_size = 0;
u8 out_keydata[RSA2048_BYTES] = {0};
/* Get eTicket RSA device key. */
EticketRsaDeviceKey *eticket_rsa_key = (EticketRsaDeviceKey*)g_eTicketRsaDeviceKey.key;
/* Perform a RSA-OAEP unwrap operation to get the encrypted titlekey. */
/* ES uses a NULL string as the label. */
ret = (rsa2048OaepDecrypt(out_keydata, sizeof(out_keydata), rsa_wrapped_titlekey, eticket_rsa_key->modulus, &(eticket_rsa_key->public_exponent), sizeof(eticket_rsa_key->public_exponent), \
@ -442,7 +442,7 @@ bool keysDecryptRsaOaepWrappedTitleKey(const void *rsa_wrapped_titlekey, void *o
LOG_MSG("RSA-OAEP titlekey decryption failed!");
}
}
return ret;
}
@ -450,18 +450,18 @@ const u8 *keysGetTicketCommonKey(u8 key_generation)
{
const u8 *ret = NULL;
u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation);
if (key_gen_val >= NcaKeyGeneration_Max)
{
LOG_MSG("Invalid key generation value! (0x%02X).", key_gen_val);
goto end;
}
SCOPED_LOCK(&g_keysetMutex)
{
if (g_keysetLoaded) ret = (const u8*)(g_ncaKeyset.ticket_common_keys[key_gen_val]);
}
end:
return ret;
}
@ -469,12 +469,12 @@ end:
const u8 *keysGetGameCardInfoKey(void)
{
const u8 *ret = NULL;
SCOPED_LOCK(&g_keysetMutex)
{
if (g_keysetLoaded) ret = (const u8*)(utilsIsDevelopmentUnit() ? g_gameCardKeyset.gc_cardinfo_key_dev : g_gameCardKeyset.gc_cardinfo_key_prod);
}
return ret;
}
@ -505,32 +505,32 @@ static bool keysRetrieveKeysFromProgramMemory(KeysMemoryInfo *info)
LOG_MSG("Invalid parameters!");
return false;
}
u8 tmp_hash[SHA256_HASH_SIZE];
bool success = false;
if (!memRetrieveProgramMemorySegment(&(info->location))) return false;
for(u32 i = 0; i < info->key_count; i++)
{
KeysMemoryKey *key = &(info->keys[i]);
bool found = false, mandatory = (key->mandatory_func != NULL ? key->mandatory_func() : true);
/* Skip key if it's not mandatory. */
if (!mandatory) continue;
/* Check destination pointer. */
if (!key->dst)
{
LOG_MSG("Invalid destination pointer for key \"%s\" in program %016lX!", key->name, info->location.program_id);
goto end;
}
/* Hash every key length-sized byte chunk in the process memory buffer until a match is found. */
for(u64 j = 0; j < info->location.data_size; j++)
{
if ((info->location.data_size - j) < key->size) break;
sha256CalculateHash(tmp_hash, info->location.data + j, key->size);
if (!memcmp(tmp_hash, key->hash, SHA256_HASH_SIZE))
{
@ -540,26 +540,26 @@ static bool keysRetrieveKeysFromProgramMemory(KeysMemoryInfo *info)
break;
}
}
if (!found)
{
LOG_MSG("Unable to locate key \"%s\" in process memory from program %016lX!", key->name, info->location.program_id);
goto end;
}
}
success = true;
end:
memFreeMemoryLocation(&(info->location));
return success;
}
static bool keysDeriveNcaHeaderKey(void)
{
Result rc = 0;
/* Derive nca_header_kek_sealed from nca_header_kek_source. */
rc = splCryptoGenerateAesKek(g_ncaKeyset.nca_header_kek_source, 0, 0, g_ncaKeyset.nca_header_kek_sealed);
if (R_FAILED(rc))
@ -567,7 +567,7 @@ static bool keysDeriveNcaHeaderKey(void)
LOG_MSG("splCryptoGenerateAesKek failed! (0x%08X) (nca_header_kek_sealed).", rc);
return false;
}
/* Derive nca_header_key from nca_header_kek_sealed and nca_header_key_source. */
rc = splCryptoGenerateAesKey(g_ncaKeyset.nca_header_kek_sealed, g_ncaKeyset.nca_header_key_source, g_ncaKeyset.nca_header_key);
if (R_FAILED(rc))
@ -575,14 +575,14 @@ static bool keysDeriveNcaHeaderKey(void)
LOG_MSG("splCryptoGenerateAesKey failed! (0x%08X) (nca_header_key) (#1).", rc);
return false;
}
rc = splCryptoGenerateAesKey(g_ncaKeyset.nca_header_kek_sealed, g_ncaKeyset.nca_header_key_source + AES_128_KEY_SIZE, g_ncaKeyset.nca_header_key + AES_128_KEY_SIZE);
if (R_FAILED(rc))
{
LOG_MSG("splCryptoGenerateAesKey failed! (0x%08X) (nca_header_key) (#2).", rc);
return false;
}
return true;
}
@ -592,18 +592,18 @@ static bool keysDeriveSealedNcaKeyAreaEncryptionKeys(void)
u32 key_cnt = 0;
u8 highest_key_gen = 0;
bool success = false;
for(u8 i = 0; i < NcaKeyAreaEncryptionKeyIndex_Count; i++)
{
/* Get pointer to current KAEK source. */
const u8 *nca_kaek_source = (const u8*)(g_ncaKeyset.nca_kaek_sources[i]);
for(u8 j = 1; j <= NcaKeyGeneration_Max; j++)
{
/* Get pointer to current sealed KAEK. */
u8 key_gen_val = (j - 1);
u8 *nca_kaek_sealed = g_ncaKeyset.nca_kaek_sealed[i][key_gen_val];
/* Derive sealed KAEK using the current KAEK source and key generation. */
rc = splCryptoGenerateAesKek(nca_kaek_source, j, 0, nca_kaek_sealed);
if (R_FAILED(rc))
@ -611,16 +611,16 @@ static bool keysDeriveSealedNcaKeyAreaEncryptionKeys(void)
//LOG_MSG("splCryptoGenerateAesKek failed for KAEK index %u and key generation %u! (0x%08X).", i, (j <= 1 ? 0 : j), rc);
break;
}
/* Update derived key count and highest key generation value. */
key_cnt++;
if (key_gen_val > highest_key_gen) highest_key_gen = key_gen_val;
}
}
success = (key_cnt > 0);
if (success) LOG_MSG("Derived %u sealed NCA KAEK(s) (%u key generation[s]).", key_cnt, highest_key_gen + 1);
return success;
}
@ -666,17 +666,17 @@ static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **v
LOG_MSG("Invalid parameters!");
return -2;
}
int ret = -1;
size_t n = 0;
ssize_t read = 0;
char *l = NULL, *k = NULL, *v = NULL, *p = NULL, *e = NULL;
/* Clear inputs beforehand. */
if (*line) free(*line);
*line = *key = *value = NULL;
errno = 0;
/* Read line. */
read = __getline(line, &n, f);
if (errno != 0 || read <= 0)
@ -685,9 +685,9 @@ static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **v
if (ret != 1) LOG_MSG("__getline failed! (0x%lX, %ld, %d, %d).", ftell(f), read, errno, ret);
goto end;
}
n = (ftell(f) - (size_t)read);
/* Check if we're dealing with an empty line. */
l = *line;
if (*l == '\n' || *l == '\r' || *l == '\0')
@ -695,7 +695,7 @@ static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **v
LOG_MSG("Empty line detected! (0x%lX, 0x%lX).", n, read);
goto end;
}
/* Not finding '\r' or '\n' is not a problem. */
/* It's possible that the last line of a file isn't actually a line (i.e., does not end in '\n'). */
/* We do want to handle those. */
@ -706,16 +706,16 @@ static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **v
} else {
e = (l + read + 1);
}
#define SKIP_SPACE(p) do { \
for(; (*p == ' ' || *p == '\t'); ++p); \
} while(0);
/* Skip leading whitespace before the key name string. */
p = l;
SKIP_SPACE(p);
k = p;
/* Validate key name string. */
for(; *p != ' ' && *p != '\t' && *p != ',' && *p != '='; ++p)
{
@ -725,14 +725,14 @@ static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **v
LOG_MSG("End of string reached while validating key name string! (#1) (0x%lX, 0x%lX, 0x%lX).", n, read, (size_t)(p - l));
goto end;
}
/* Convert uppercase characters to lowercase. */
if (*p >= 'A' && *p <= 'Z')
{
*p = ('a' + (*p - 'A'));
continue;
}
/* Handle unsupported characters. */
if (*p != '_' && (*p < '0' || *p > '9') && (*p < 'a' || *p > 'z'))
{
@ -740,14 +740,14 @@ static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **v
goto end;
}
}
/* Bail if the final ++p put us at the end of string. */
if (*p == '\0')
{
LOG_MSG("End of string reached while validating key name string! (#2) (0x%lX, 0x%lX, 0x%lX).", n, read, (size_t)(p - l));
goto end;
}
/* We should be at the end of the key name string now and either whitespace or [,=] follows. */
if (*p == '=' || *p == ',')
{
@ -756,29 +756,29 @@ static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **v
/* Skip leading whitespace before [,=]. */
*p++ = '\0';
SKIP_SPACE(p);
if (*p != '=' && *p != ',')
{
LOG_MSG("Unable to find expected [,=]! (0x%lX, 0x%lX, 0x%lX).", n, read, (size_t)(p - l));
goto end;
}
*p++ = '\0';
}
/* Empty key name string is an error. */
if (*k == '\0')
{
LOG_MSG("Key name string empty! (0x%lX, 0x%lX).", n, read);
goto end;
}
/* Skip trailing whitespace after [,=]. */
SKIP_SPACE(p);
v = p;
#undef SKIP_SPACE
/* Validate value string. */
for(; p < e && *p != ' ' && *p != '\t'; ++p)
{
@ -788,14 +788,14 @@ static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **v
LOG_MSG("End of string reached while validating value string! (0x%lX, 0x%lX, 0x%lX, %s).", n, read, (size_t)(p - l), k);
goto end;
}
/* Convert uppercase characters to lowercase. */
if (*p >= 'A' && *p <= 'F')
{
*p = ('a' + (*p - 'A'));
continue;
}
/* Handle unsupported characters. */
if ((*p < '0' || *p > '9') && (*p < 'a' || *p > 'f'))
{
@ -803,7 +803,7 @@ static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **v
goto end;
}
}
/* We should be at the end of the value string now and whitespace may optionally follow. */
l = p;
if (p < e)
@ -812,35 +812,35 @@ static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **v
/* Make sure there's no additional data after this. */
*p++ = '\0';
for(; p < e && (*p == ' ' || *p == '\t'); ++p);
if (p < e)
{
LOG_MSG("Additional data detected after value string and before line end! (0x%lX, 0x%lX, 0x%lX, %s).", n, read, (size_t)(p - *line), k);
goto end;
}
}
/* Empty value string and value string length not being a multiple of 2 are both errors. */
if (*v == '\0' || ((l - v) % 2) != 0)
{
LOG_MSG("Invalid value string length! (0x%lX, 0x%lX, 0x%lX, %s).", n, read, (size_t)(l - v), k);
goto end;
}
/* Update pointers. */
*key = k;
*value = v;
/* Update return value. */
ret = 0;
end:
if (ret != 0)
{
if (*line) free(*line);
*line = *key = *value = NULL;
}
return ret;
}
@ -856,21 +856,21 @@ static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 siz
{
u32 hex_str_len = (2 * size);
size_t value_len = 0;
if (!out || !key || !*key || !value || !(value_len = strlen(value)) || !size)
{
LOG_MSG("Invalid parameters!");
return false;
}
if (value_len != hex_str_len)
{
LOG_MSG("Key \"%s\" must be %u hex digits long!", key, hex_str_len);
return false;
}
memset(out, 0, size);
for(u32 i = 0; i < hex_str_len; i++)
{
char val = keysConvertHexDigitToBinary(value[i]);
@ -879,11 +879,11 @@ static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 siz
LOG_MSG("Invalid hex character in key \"%s\" at position %u!", key, i);
return false;
}
if ((i & 1) == 0) val <<= 4;
out[i >> 1] |= val;
}
return true;
}
@ -896,59 +896,59 @@ static bool keysReadKeysFromFile(void)
char test_name[0x40] = {0};
bool eticket_rsa_kek_available = false;
const char *keys_file_path = (utilsIsDevelopmentUnit() ? DEV_KEYS_FILE_PATH : PROD_KEYS_FILE_PATH);
keys_file = fopen(keys_file_path, "rb");
if (!keys_file)
{
LOG_MSG("Unable to open \"%s\" to retrieve keys!", keys_file_path);
return false;
}
#define PARSE_HEX_KEY(name, out, decl) \
if (!strcmp(key, name) && keysParseHexKey(out, key, value, sizeof(out))) { \
key_count++; \
decl; \
}
#define PARSE_HEX_KEY_WITH_INDEX(name, out) \
snprintf(test_name, sizeof(test_name), "%s_%02x", name, i); \
PARSE_HEX_KEY(test_name, out, break);
while(true)
{
/* Get key and value strings from the current line. */
/* Break from the while loop if EOF is reached or if an I/O error occurs. */
ret = keysGetKeyAndValueFromFile(keys_file, &line, &key, &value);
if (ret == 1 || ret == -2) break;
/* Ignore malformed or empty lines. */
if (ret != 0 || !key || !value) continue;
PARSE_HEX_KEY("eticket_rsa_kek", g_ncaKeyset.eticket_rsa_kek, eticket_rsa_kek_available = true; continue);
/* This only appears on consoles that use the new PRODINFO key generation scheme. */
PARSE_HEX_KEY("eticket_rsa_kek_personalized", g_ncaKeyset.eticket_rsa_kek_personalized, eticket_rsa_kek_available = true; continue);
for(u32 i = 0; i < NcaKeyGeneration_Max; i++)
{
PARSE_HEX_KEY_WITH_INDEX("titlekek", g_ncaKeyset.ticket_common_keys[i]);
PARSE_HEX_KEY_WITH_INDEX("key_area_key_application", g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_Application][i]);
PARSE_HEX_KEY_WITH_INDEX("key_area_key_ocean", g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_Ocean][i]);
PARSE_HEX_KEY_WITH_INDEX("key_area_key_system", g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_System][i]);
}
}
#undef PARSE_HEX_KEY_WITH_INDEX
#undef PARSE_HEX_KEY
if (line) free(line);
fclose(keys_file);
if (key_count)
{
LOG_MSG("Loaded %u key(s) from \"%s\".", key_count, keys_file_path);
@ -956,13 +956,13 @@ static bool keysReadKeysFromFile(void)
LOG_MSG("Unable to parse keys from \"%s\"! (keys file empty?).", keys_file_path);
return false;
}
if (!eticket_rsa_kek_available)
{
LOG_MSG("\"eticket_rsa_kek\" unavailable in \"%s\"!", keys_file_path);
return false;
}
return true;
}
@ -973,7 +973,7 @@ static bool keysGetDecryptedEticketRsaDeviceKey(void)
const u8 *eticket_rsa_kek = NULL;
EticketRsaDeviceKey *eticket_rsa_key = NULL;
Aes128CtrContext eticket_aes_ctx = {0};
/* Get eTicket RSA device key. */
rc = setcalGetEticketDeviceKey(&g_eTicketRsaDeviceKey);
if (R_FAILED(rc))
@ -981,15 +981,15 @@ static bool keysGetDecryptedEticketRsaDeviceKey(void)
LOG_MSG("setcalGetEticketDeviceKey failed! (0x%08X).", rc);
return false;
}
/* Get eTicket RSA device key encryption key. */
eticket_rsa_kek = (const u8*)(g_eTicketRsaDeviceKey.generation > 0 ? g_ncaKeyset.eticket_rsa_kek_personalized : g_ncaKeyset.eticket_rsa_kek);
/* Decrypt eTicket RSA device key. */
eticket_rsa_key = (EticketRsaDeviceKey*)g_eTicketRsaDeviceKey.key;
aes128CtrContextCreate(&eticket_aes_ctx, eticket_rsa_kek, eticket_rsa_key->ctr);
aes128CtrCrypt(&eticket_aes_ctx, &(eticket_rsa_key->private_exponent), &(eticket_rsa_key->private_exponent), sizeof(EticketRsaDeviceKey) - sizeof(eticket_rsa_key->ctr));
/* Public exponent value must be 0x10001. */
/* It is stored using big endian byte order. */
public_exponent = __builtin_bswap32(eticket_rsa_key->public_exponent);
@ -998,14 +998,14 @@ static bool keysGetDecryptedEticketRsaDeviceKey(void)
LOG_MSG("Invalid public exponent for decrypted eTicket RSA device key! Wrong keys? (0x%08X).", public_exponent);
return false;
}
/* Test RSA key pair. */
if (!keysTestEticketRsaDeviceKey(&(eticket_rsa_key->public_exponent), eticket_rsa_key->private_exponent, eticket_rsa_key->modulus))
{
LOG_MSG("eTicket RSA device key test failed! Wrong keys?");
return false;
}
return true;
}
@ -1016,43 +1016,43 @@ static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void
LOG_MSG("Invalid parameters!");
return false;
}
Result rc = 0;
u8 x[RSA2048_BYTES] = {0}, y[RSA2048_BYTES] = {0}, z[RSA2048_BYTES] = {0};
/* 0xCAFEBABE. */
x[0xFC] = 0xCA;
x[0xFD] = 0xFE;
x[0xFE] = 0xBA;
x[0xFF] = 0xBE;
rc = splUserExpMod(x, n, d, RSA2048_BYTES, y);
if (R_FAILED(rc))
{
LOG_MSG("splUserExpMod failed! (#1) (0x%08X).", rc);
return false;
}
rc = splUserExpMod(y, n, e, 4, z);
if (R_FAILED(rc))
{
LOG_MSG("splUserExpMod failed! (#2) (0x%08X).", rc);
return false;
}
if (memcmp(x, z, RSA2048_BYTES) != 0)
{
LOG_MSG("Invalid RSA key pair!");
return false;
}
return true;
}
static bool keysDeriveGameCardKeys(void)
{
Result rc = 0;
/* Derive gc_cardinfo_kek_sealed from gc_cardinfo_kek_source. */
rc = splCryptoGenerateAesKek(g_gameCardKeyset.gc_cardinfo_kek_source, 0, 0, g_gameCardKeyset.gc_cardinfo_kek_sealed);
if (R_FAILED(rc))
@ -1060,7 +1060,7 @@ static bool keysDeriveGameCardKeys(void)
LOG_MSG("splCryptoGenerateAesKek failed! (0x%08X) (gc_cardinfo_kek_sealed).", rc);
return false;
}
/* Derive gc_cardinfo_key_prod from gc_cardinfo_kek_sealed and gc_cardinfo_key_prod_source. */
rc = splCryptoGenerateAesKey(g_gameCardKeyset.gc_cardinfo_kek_sealed, g_gameCardKeyset.gc_cardinfo_key_prod_source, g_gameCardKeyset.gc_cardinfo_key_prod);
if (R_FAILED(rc))
@ -1068,7 +1068,7 @@ static bool keysDeriveGameCardKeys(void)
LOG_MSG("splCryptoGenerateAesKey failed! (0x%08X) (gc_cardinfo_key_prod).", rc);
return false;
}
/* Derive gc_cardinfo_key_dev from gc_cardinfo_kek_sealed and gc_cardinfo_key_dev_source. */
rc = splCryptoGenerateAesKey(g_gameCardKeyset.gc_cardinfo_kek_sealed, g_gameCardKeyset.gc_cardinfo_key_dev_source, g_gameCardKeyset.gc_cardinfo_key_dev);
if (R_FAILED(rc))
@ -1076,6 +1076,6 @@ static bool keysDeriveGameCardKeys(void)
LOG_MSG("splCryptoGenerateAesKey failed! (0x%08X) (gc_cardinfo_key_dev).", rc);
return false;
}
return true;
}

View file

@ -31,38 +31,38 @@ bool legalInfoInitializeContext(LegalInfoContext *out, NcaContext *nca_ctx)
LOG_MSG("Invalid parameters!");
return false;
}
RomFileSystemContext romfs_ctx = {0};
RomFileSystemFileEntry *xml_entry = NULL;
bool success = false;
/* Free output context beforehand. */
legalInfoFreeContext(out);
/* Initialize RomFS context. */
if (!romfsInitializeContext(&romfs_ctx, &(nca_ctx->fs_ctx[0]), NULL))
{
LOG_MSG("Failed to initialize RomFS context!");
goto end;
}
/* Retrieve RomFS file entry for 'legalinfo.xml'. */
if (!(xml_entry = romfsGetFileEntryByPath(&romfs_ctx, "/legalinfo.xml")))
{
LOG_MSG("Failed to retrieve file entry for \"legalinfo.xml\" from RomFS!");
goto end;
}
//LOG_MSG("Found 'legalinfo.xml' entry in LegalInformation NCA \"%s\".", nca_ctx->content_id_str);
/* Verify XML size. */
if (!xml_entry->size)
{
LOG_MSG("Invalid XML size!");
goto end;
}
/* Allocate memory for the XML. */
out->authoring_tool_xml_size = xml_entry->size;
if (!(out->authoring_tool_xml = malloc(out->authoring_tool_xml_size)))
@ -70,27 +70,27 @@ bool legalInfoInitializeContext(LegalInfoContext *out, NcaContext *nca_ctx)
LOG_MSG("Failed to allocate memory for the XML!");
goto end;
}
/* Read NACP data into memory buffer. */
if (!romfsReadFileEntryData(&romfs_ctx, xml_entry, out->authoring_tool_xml, out->authoring_tool_xml_size, 0))
{
LOG_MSG("Failed to read XML!");
goto end;
}
/* Update NCA context pointer in output context. */
out->nca_ctx = nca_ctx;
/* Update content type context info in NCA context. */
nca_ctx->content_type_ctx = out;
nca_ctx->content_type_ctx_patch = false;
success = true;
end:
romfsFreeContext(&romfs_ctx);
if (!success) legalInfoFreeContext(out);
return success;
}

View file

@ -44,7 +44,7 @@ bool memRetrieveProgramMemorySegment(MemoryLocation *location)
LOG_MSG("Invalid parameters!");
return false;
}
bool ret = false;
SCOPED_LOCK(&g_memMutex) ret = memRetrieveProgramMemory(location, true);
return ret;
@ -57,7 +57,7 @@ bool memRetrieveFullProgramMemory(MemoryLocation *location)
LOG_MSG("Invalid parameters!");
return false;
}
bool ret = false;
SCOPED_LOCK(&g_memMutex) ret = memRetrieveProgramMemory(location, false);
return ret;
@ -67,16 +67,16 @@ static bool memRetrieveProgramMemory(MemoryLocation *location, bool is_segment)
{
Result rc = 0;
Handle debug_handle = INVALID_HANDLE;
MemoryInfo mem_info = {0};
u32 page_info = 0;
u64 addr = 0, last_text_addr = 0;
u8 segment = 1, mem_type = 0;
u8 *tmp = NULL;
bool success = true;
/* Make sure we have access to debug SVC calls. */
if (!(envIsSyscallHinted(0x60) && /* svcDebugActiveProcess. */
envIsSyscallHinted(0x63) && /* svcGetDebugEvent. */
@ -87,23 +87,23 @@ static bool memRetrieveProgramMemory(MemoryLocation *location, bool is_segment)
LOG_MSG("Debug SVC permissions not available!");
return false;
}
/* Clear output MemoryLocation element. */
memFreeMemoryLocation(location);
/* LOG_MSG() will be useless if the target program is the FS sysmodule. */
/* This is because any FS I/O operation *will* lock up the console while FS itself is being debugged. */
/* So we'll just temporarily log data to a char array using LOG_MSG_BUF(), then write it all out after calling svcCloseHandle(). */
/* However, we must prevent other threads from logging data as well in order to avoid a lock up, so we'll temporarily lock the logfile mutex. */
logControlMutex(true);
/* Retrieve debug handle by program ID. */
if (!memRetrieveDebugHandleFromProgramById(&debug_handle, location->program_id))
{
MEMLOG("Unable to retrieve debug handle for program %016lX!", location->program_id);
goto end;
}
if (is_segment && location->program_id == FS_SYSMODULE_TID)
{
/* If dealing with FS, locate the "real" .text segment, since Atmosphère emuMMC has two. */
@ -115,16 +115,16 @@ static bool memRetrieveProgramMemory(MemoryLocation *location, bool is_segment)
success = false;
goto end;
}
mem_type = (u8)(mem_info.type & 0xFF);
if ((mem_info.perm & Perm_X) && (mem_type == MemType_CodeStatic || mem_type == MemType_CodeMutable)) last_text_addr = mem_info.addr;
addr = (mem_info.addr + mem_info.size);
} while(addr != 0);
addr = last_text_addr;
}
do {
rc = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr);
if (R_FAILED(rc))
@ -133,9 +133,9 @@ static bool memRetrieveProgramMemory(MemoryLocation *location, bool is_segment)
success = false;
break;
}
mem_type = (u8)(mem_info.type & 0xFF);
/* Code to allow for bitmasking segments. */
if ((mem_info.perm & Perm_R) && ((!is_segment && !mem_info.attr && (location->program_id != FS_SYSMODULE_TID || (location->program_id == FS_SYSMODULE_TID && mem_type != MemType_Unmapped && \
mem_type != MemType_Io && mem_type != MemType_ThreadLocal && mem_type != MemType_Reserved))) || (is_segment && (mem_type == MemType_CodeStatic || mem_type == MemType_CodeMutable) && \
@ -149,10 +149,10 @@ static bool memRetrieveProgramMemory(MemoryLocation *location, bool is_segment)
success = false;
break;
}
location->data = tmp;
tmp = NULL;
rc = svcReadDebugProcessMemory(location->data + location->data_size, debug_handle, mem_info.addr, mem_info.size);
if (R_FAILED(rc))
{
@ -160,40 +160,40 @@ static bool memRetrieveProgramMemory(MemoryLocation *location, bool is_segment)
success = false;
break;
}
location->data_size += mem_info.size;
}
addr = (mem_info.addr + mem_info.size);
} while(addr != 0 && segment < BIT(3));
end:
/* Close debug handle. */
if (debug_handle != INVALID_HANDLE) svcCloseHandle(debug_handle);
/* Unlock logfile mutex. */
logControlMutex(false);
if (success && (!location->data || !location->data_size))
{
MEMLOG("Unable to locate readable program memory pages for %016lX that match the required criteria!", location->program_id);
success = false;
}
if (!success) memFreeMemoryLocation(location);
/* Write log buffer data. This will do nothing if the log buffer length is zero. */
logWriteStringToLogFile(g_memLogBuf);
/* Free memory log buffer. */
if (g_memLogBuf)
{
free(g_memLogBuf);
g_memLogBuf = NULL;
}
g_memLogBufSize = 0;
return success;
}
@ -204,14 +204,14 @@ static bool memRetrieveDebugHandleFromProgramById(Handle *out, u64 program_id)
MEMLOG("Invalid parameters!");
return false;
}
Result rc = 0;
u64 pid = 0, d[8] = {0};
Handle debug_handle = INVALID_HANDLE;
u32 i = 0, num_processes = 0;
u64 *pids = NULL;
if (program_id > BOOT_SYSMODULE_TID && program_id != SPL_SYSMODULE_TID)
{
/* If not a kernel process, get PID from pm:dmnt. */
@ -221,7 +221,7 @@ static bool memRetrieveDebugHandleFromProgramById(Handle *out, u64 program_id)
MEMLOG("pmdmntGetProcessId failed for program %016lX! (0x%08X).", program_id, rc);
return false;
}
/* Retrieve debug handle right away. */
rc = svcDebugActiveProcess(&debug_handle, pid);
if (R_FAILED(rc))
@ -237,7 +237,7 @@ static bool memRetrieveDebugHandleFromProgramById(Handle *out, u64 program_id)
MEMLOG("Failed to allocate memory for PID list!");
return false;
}
rc = svcGetProcessList((s32*)&num_processes, pids, 300);
if (R_FAILED(rc))
{
@ -245,35 +245,35 @@ static bool memRetrieveDebugHandleFromProgramById(Handle *out, u64 program_id)
free(pids);
return false;
}
/* Perform a lookup using the retrieved process list. */
for(i = 0; i < num_processes; i++)
{
/* Retrieve debug handle for the current PID. */
rc = svcDebugActiveProcess(&debug_handle, pids[i]);
if (R_FAILED(rc)) continue;
/* Get debug event using the debug handle. */
/* This will let us know the program ID from the current PID. */
rc = svcGetDebugEvent((u8*)&d, debug_handle);
if (R_SUCCEEDED(rc) && d[2] == program_id) break;
/* No match. Close debug handle and keep looking for our program. */
svcCloseHandle(debug_handle);
debug_handle = INVALID_HANDLE;
}
free(pids);
if (i == num_processes)
{
MEMLOG("Unable to find program %016lX in kernel process list! (0x%08X).", program_id, rc);
return false;
}
}
/* Set output debug handle. */
*out = debug_handle;
return true;
}

View file

@ -236,136 +236,136 @@ bool nacpInitializeContext(NacpContext *out, NcaContext *nca_ctx)
LOG_MSG("Invalid parameters!");
return false;
}
const char *language_str = NULL;
char icon_path[0x80] = {0};
RomFileSystemFileEntry *icon_entry = NULL;
NacpIconContext *tmp_icon_ctx = NULL;
bool success = false;
/* Free output context beforehand. */
nacpFreeContext(out);
/* Initialize RomFS context. */
if (!romfsInitializeContext(&(out->romfs_ctx), &(nca_ctx->fs_ctx[0]), NULL))
{
LOG_MSG("Failed to initialize RomFS context!");
goto end;
}
/* Retrieve RomFS file entry for 'control.nacp'. */
if (!(out->romfs_file_entry = romfsGetFileEntryByPath(&(out->romfs_ctx), "/control.nacp")))
{
LOG_MSG("Failed to retrieve file entry for \"control.nacp\" from RomFS!");
goto end;
}
//LOG_MSG("Found 'control.nacp' entry in Control NCA \"%s\".", nca_ctx->content_id_str);
/* Verify NACP size. */
if (out->romfs_file_entry->size != sizeof(_NacpStruct))
{
LOG_MSG("Invalid NACP size!");
goto end;
}
/* Allocate memory for the NACP data. */
if (!(out->data = malloc(sizeof(_NacpStruct))))
{
LOG_MSG("Failed to allocate memory for the NACP data!");
goto end;
}
/* Read NACP data into memory buffer. */
if (!romfsReadFileEntryData(&(out->romfs_ctx), out->romfs_file_entry, out->data, sizeof(_NacpStruct), 0))
{
LOG_MSG("Failed to read NACP data!");
goto end;
}
/* Calculate SHA-256 checksum for the whole NACP. */
sha256CalculateHash(out->data_hash, out->data, sizeof(_NacpStruct));
/* Retrieve NACP icon data. */
for(u8 i = 0; i < NacpSupportedLanguage_Count; i++)
{
NacpIconContext *icon_ctx = NULL;
/* Get language string. */
language_str = nacpGetLanguageString(i);
/* Check if the current language is supported. */
if (!nacpCheckBitflagField(&(out->data->supported_language), sizeof(out->data->supported_language) * 8, i))
{
//LOG_MSG("\"%s\" language not supported (flag 0x%08X, index %u).", language_str, out->data->supported_language, i);
continue;
}
/* Generate icon path. */
sprintf(icon_path, "/icon_%s.dat", language_str);
/* Retrieve RomFS file entry for this icon. */
if (!(icon_entry = romfsGetFileEntryByPath(&(out->romfs_ctx), icon_path)))
{
//LOG_MSG("\"%s\" file entry not found (flag 0x%08X, index %u).", icon_path, out->data->supported_language, i);
continue;
}
/* Check icon size. */
if (!icon_entry->size || icon_entry->size > NACP_MAX_ICON_SIZE)
{
LOG_MSG("Invalid NACP icon size!");
goto end;
}
/* Reallocate icon context buffer. */
if (!(tmp_icon_ctx = realloc(out->icon_ctx, (out->icon_count + 1) * sizeof(NacpIconContext))))
{
LOG_MSG("Failed to reallocate NACP icon context buffer!");
goto end;
}
out->icon_ctx = tmp_icon_ctx;
tmp_icon_ctx = NULL;
icon_ctx = &(out->icon_ctx[out->icon_count]);
memset(icon_ctx, 0, sizeof(NacpIconContext));
/* Allocate memory for this icon data. */
if (!(icon_ctx->icon_data = malloc(icon_entry->size)))
{
LOG_MSG("Failed to allocate memory for NACP icon data!");
goto end;
}
/* Read icon data. */
if (!romfsReadFileEntryData(&(out->romfs_ctx), icon_entry, icon_ctx->icon_data, icon_entry->size, 0))
{
LOG_MSG("Failed to read NACP icon data!");
goto end;
}
/* Fill icon context. */
icon_ctx->language = i;
icon_ctx->icon_size = icon_entry->size;
/* Update icon count. */
out->icon_count++;
}
/* Update NCA context pointer in output context. */
out->nca_ctx = nca_ctx;
/* Update content type context info in NCA context. */
nca_ctx->content_type_ctx = out;
nca_ctx->content_type_ctx_patch = false;
success = true;
end:
if (!success) nacpFreeContext(out);
return success;
}
@ -376,13 +376,13 @@ bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_scre
LOG_MSG("Invalid parameters!");
return false;
}
_NacpStruct *data = nacp_ctx->data;
u8 nacp_hash[SHA256_HASH_SIZE] = {0};
/* Check if we're not patching anything. */
if (!patch_sua && !patch_screenshot && !patch_video_capture && !patch_hdcp) return true;
/* Patch StartupUserAccount, StartupUserAccountOption and UserAccountSwitchLock. */
if (patch_sua)
{
@ -390,16 +390,16 @@ bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_scre
data->startup_user_account_option &= ~NacpStartupUserAccountOption_IsOptional;
data->user_account_switch_lock = NacpUserAccountSwitchLock_Disable;
}
/* Patch Screenshot. */
if (patch_screenshot) data->screenshot = NacpScreenshot_Allow;
/* Patch VideoCapture. */
if (patch_video_capture) data->video_capture = NacpVideoCapture_Enable;
/* Patch Hdcp. */
if (patch_hdcp) data->hdcp = NacpHdcp_None;
/* Check if we really need to generate this patch. */
sha256CalculateHash(nacp_hash, data, sizeof(_NacpStruct));
if (!memcmp(nacp_hash, nacp_ctx->data_hash, sizeof(nacp_hash)))
@ -407,17 +407,17 @@ bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_scre
LOG_MSG("Skipping NACP patching - no flags have changed.");
return true;
}
/* Generate RomFS file entry patch. */
if (!romfsGenerateFileEntryPatch(&(nacp_ctx->romfs_ctx), nacp_ctx->romfs_file_entry, data, sizeof(_NacpStruct), 0, &(nacp_ctx->nca_patch)))
{
LOG_MSG("Failed to generate RomFS file entry patch!");
return false;
}
/* Update NCA content type context patch status. */
nacp_ctx->nca_ctx->content_type_ctx_patch = true;
return true;
}
@ -425,13 +425,13 @@ void nacpWriteNcaPatch(NacpContext *nacp_ctx, void *buf, u64 buf_size, u64 buf_o
{
NcaContext *nca_ctx = NULL;
RomFileSystemFileEntryPatch *nca_patch = (nacp_ctx ? &(nacp_ctx->nca_patch) : NULL);
/* Using nacpIsValidContext() here would probably take up precious CPU cycles. */
if (!nca_patch || nca_patch->written || !(nca_ctx = nacp_ctx->nca_ctx) || nca_ctx->content_type != NcmContentType_Control || !nca_ctx->content_type_ctx_patch) return;
/* Attempt to write RomFS file entry patch. */
romfsWriteFileEntryPatchToMemoryBuffer(&(nacp_ctx->romfs_ctx), nca_patch, buf, buf_size, buf_offset);
/* Check if we need to update the NCA content type context patch status. */
if (nca_patch->written)
{
@ -447,44 +447,44 @@ bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 requir
LOG_MSG("Invalid parameters!");
return false;
}
_NacpStruct *nacp = nacp_ctx->data;
Version app_ver = { .value = version };
u8 i = 0, count = 0;
char *xml_buf = NULL;
u64 xml_buf_size = 0;
u8 icon_hash[SHA256_HASH_SIZE] = {0};
char icon_hash_str[SHA256_HASH_SIZE + 1] = {0};
u8 null_key[0x10] = {0};
char key_str[0x21] = {0};
bool ndcc_sgc_available = false, ndcc_rgc_available = false;
NacpNeighborDetectionClientConfiguration *ndcc = &(nacp->neighbor_detection_client_configuration);
NacpRequiredAddOnContentsSetBinaryDescriptor *raocsbd = &(nacp->required_add_on_contents_set_binary_descriptor);
bool raocsbd_available = false;
bool alrv_available = false;
bool success = false;
/* Free AuthoringTool-like XML data if needed. */
if (nacp_ctx->authoring_tool_xml) free(nacp_ctx->authoring_tool_xml);
nacp_ctx->authoring_tool_xml = NULL;
nacp_ctx->authoring_tool_xml_size = 0;
if (!NACP_ADD_FMT_STR_T1("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" \
"<Application>\n")) goto end;
/* Title. */
for(i = 0, count = 0; i < NacpLanguage_Count; i++)
{
NacpTitle *title = &(nacp->title[i]);
if (!*(title->name) || !*(title->publisher)) continue;
if (!NACP_ADD_FMT_STR_T1(" <Title>\n" \
" <Language>%s</Language>\n" \
" <Name>%s</Name>\n" \
@ -493,133 +493,133 @@ bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 requir
nacpGetLanguageString(i), \
title->name, \
title->publisher)) goto end;
count++;
}
if (!count && !NACP_ADD_FMT_STR_T1(" <Title />\n")) goto end;
/* Isbn. */
if (!NACP_ADD_STR("Isbn", nacp->isbn)) goto end;
/* StartupUserAccount. */
if (!NACP_ADD_ENUM("StartupUserAccount", nacp->startup_user_account, nacpGetStartupUserAccountString)) goto end;
/* StartupUserAccountOption. */
if (!NACP_ADD_BITFLAG("StartupUserAccountOption", &(nacp->startup_user_account_option), sizeof(nacp->startup_user_account_option), NacpStartupUserAccountOption_Count, \
nacpGetStartupUserAccountOptionString, true)) goto end;
/* UserAccountSwitchLock. */
if (!NACP_ADD_ENUM("UserAccountSwitchLock", nacp->user_account_switch_lock, nacpGetUserAccountSwitchLockString)) goto end;
/* Attribute. */
if (!NACP_ADD_BITFLAG("Attribute", &(nacp->attribute), sizeof(nacp->attribute), NacpAttribute_Count, nacpGetAttributeString, false)) goto end;
/* ParentalControl. */
if (!NACP_ADD_BITFLAG("ParentalControl", &(nacp->parental_control), sizeof(nacp->parental_control), NacpParentalControl_Count, nacpGetParentalControlString, false)) goto end;
/* SupportedLanguage. */
if (!NACP_ADD_BITFLAG("SupportedLanguage", &(nacp->supported_language), sizeof(nacp->supported_language), NacpSupportedLanguage_Count, nacpGetLanguageString, false)) goto end;
/* Screenshot. */
if (!NACP_ADD_ENUM("Screenshot", nacp->screenshot, nacpGetScreenshotString)) goto end;
/* VideoCapture. */
if (!NACP_ADD_ENUM("VideoCapture", nacp->video_capture, nacpGetVideoCaptureString)) goto end;
/* PresenceGroupId. */
if (!NACP_ADD_U64("PresenceGroupId", nacp->presence_group_id, true, true)) goto end;
/* DisplayVersion. */
if (!NACP_ADD_STR("DisplayVersion", nacp->display_version)) goto end;
/* Rating. */
for(i = 0, count = 0; i < NacpRatingAgeOrganization_Count; i++)
{
s8 age = *(((s8*)&(nacp->rating_age)) + i);
if (age < 0) continue;
if (!NACP_ADD_FMT_STR_T1(" <Rating>\n" \
" <Organization>%s</Organization>\n" \
" <Age>%d</Age>\n" \
" </Rating>\n", \
nacpGetRatingAgeOrganizationString(i), \
age)) goto end;
count++;
}
if (!count && !NACP_ADD_FMT_STR_T1(" <Rating />\n")) goto end;
/* DataLossConfirmation. */
if (!NACP_ADD_ENUM("DataLossConfirmation", nacp->data_loss_confirmation, nacpGetDataLossConfirmationString)) goto end;
/* PlayLogPolicy. */
if (!NACP_ADD_ENUM("PlayLogPolicy", nacp->play_log_policy, nacpGetPlayLogPolicyString)) goto end;
/* SaveDataOwnerId. */
if (!NACP_ADD_U64("SaveDataOwnerId", nacp->save_data_owner_id, true, true)) goto end;
/* UserAccountSaveDataSize. */
if (!NACP_ADD_U64("UserAccountSaveDataSize", (u64)nacp->user_account_save_data_size, true, true)) goto end;
/* UserAccountSaveDataJournalSize. */
if (!NACP_ADD_U64("UserAccountSaveDataJournalSize", (u64)nacp->user_account_save_data_journal_size, true, true)) goto end;
/* UserAccountSaveDataTotalSize. */
if (!NACP_ADD_U64("UserAccountSaveDataTotalSize", (u64)(nacp->user_account_save_data_size + nacp->user_account_save_data_journal_size), true, true)) goto end;
/* DeviceSaveDataSize. */
if (!NACP_ADD_U64("DeviceSaveDataSize", (u64)nacp->device_save_data_size, true, true)) goto end;
/* DeviceSaveDataJournalSize. */
if (!NACP_ADD_U64("DeviceSaveDataJournalSize", (u64)nacp->device_save_data_journal_size, true, true)) goto end;
/* BcatDeliveryCacheStorageSize. */
if (!NACP_ADD_U64("BcatDeliveryCacheStorageSize", (u64)nacp->bcat_delivery_cache_storage_size, true, true)) goto end;
/* ApplicationErrorCodeCategory. */
if (!NACP_ADD_STR("ApplicationErrorCodeCategory", nacp->application_error_code_category)) goto end;
/* AddOnContentBaseId. */
if (!NACP_ADD_U64("AddOnContentBaseId", nacp->add_on_content_base_id, true, true)) goto end;
/* Version. */
if (!NACP_ADD_U32("Version", version, false, false)) goto end;
/* ReleaseVersion. */
if (!NACP_ADD_U32("ReleaseVersion", app_ver.application_version.release_ver, false, false)) goto end;
/* PrivateVersion. */
if (!NACP_ADD_U32("PrivateVersion", app_ver.application_version.private_ver, false, false)) goto end;
/* LogoType. */
if (!NACP_ADD_ENUM("LogoType", nacp->logo_type, nacpGetLogoTypeString)) goto end;
/* RequiredSystemVersion. */
if (!NACP_ADD_U32("RequiredSystemVersion", required_system_version, false, false)) goto end;
/* LocalCommunicationId. */
for(i = 0; i < 0x8; i++)
{
if (!nacp->local_communication_id[i]) continue;
if (!NACP_ADD_U64("LocalCommunicationId", nacp->local_communication_id[i], true, true)) goto end;
}
/* LogoHandling. */
if (!NACP_ADD_ENUM("LogoHandling", nacp->logo_handling, nacpGetLogoHandlingString)) goto end;
/* Icon. */
for(i = 0, count = 0; i < nacp_ctx->icon_count; i++)
{
NacpIconContext *icon_ctx = &(nacp_ctx->icon_ctx[i]);
/* Calculate icon hash. */
sha256CalculateHash(icon_hash, icon_ctx->icon_data, icon_ctx->icon_size);
/* Generate icon hash string. Only the first half from the hash is used. */
utilsGenerateHexStringFromData(icon_hash_str, sizeof(icon_hash_str), icon_hash, sizeof(icon_hash) / 2, false);
/* Add XML element. */
if (!NACP_ADD_FMT_STR_T1(" <Icon>\n" \
" <Language>%s</Language>\n" \
@ -630,118 +630,118 @@ bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 requir
" </Icon>\n", \
nacpGetLanguageString(icon_ctx->language), \
icon_hash_str)) goto end;
count++;
}
if (!count && !NACP_ADD_FMT_STR_T1(" <Icon />\n")) goto end;
/* HtmlDocumentPath, LegalInformationFilePath and AccessibleUrlsFilePath. Unused but kept anyway. */
if (!NACP_ADD_FMT_STR_T1(" <HtmlDocumentPath UseEnvironmentVariable=\"false\" />\n" \
" <LegalInformationFilePath UseEnvironmentVariable=\"false\" />\n" \
" <AccessibleUrlsFilePath UseEnvironmentVariable=\"false\" />\n")) goto end;
/* SeedForPseudoDeviceId. */
if (!NACP_ADD_U64("SeedForPseudoDeviceId", nacp->seed_for_pseudo_device_id, true, false)) goto end;
/* BcatPassphrase. */
if (!NACP_ADD_STR("BcatPassphrase", nacp->bcat_passphrase)) goto end;
/* AddOnContentRegistrationType. */
if (!NACP_ADD_ENUM("AddOnContentRegistrationType", nacp->add_on_content_registration_type, nacpGetAddOnContentRegistrationTypeString)) goto end;
/* UserAccountSaveDataSizeMax. */
if (!NACP_ADD_U64("UserAccountSaveDataSizeMax", (u64)nacp->user_account_save_data_size_max, true, true)) goto end;
/* UserAccountSaveDataJournalSizeMax. */
if (!NACP_ADD_U64("UserAccountSaveDataJournalSizeMax", (u64)nacp->user_account_save_data_journal_size_max, true, true)) goto end;
/* DeviceSaveDataSizeMax. */
if (!NACP_ADD_U64("DeviceSaveDataSizeMax", (u64)nacp->device_save_data_size_max, true, true)) goto end;
/* DeviceSaveDataJournalSizeMax. */
if (!NACP_ADD_U64("DeviceSaveDataJournalSizeMax", (u64)nacp->device_save_data_journal_size_max, true, true)) goto end;
/* TemporaryStorageSize. */
if (!NACP_ADD_U64("TemporaryStorageSize", (u64)nacp->temporary_storage_size, true, true)) goto end;
/* CacheStorageSize. */
if (!NACP_ADD_U64("CacheStorageSize", (u64)nacp->cache_storage_size, true, true)) goto end;
/* CacheStorageJournalSize. */
if (!NACP_ADD_U64("CacheStorageJournalSize", (u64)nacp->cache_storage_journal_size, true, true)) goto end;
/* CacheStorageDataAndJournalSizeMax. */
if (!NACP_ADD_U64("CacheStorageDataAndJournalSizeMax", (u64)nacp->cache_storage_data_and_journal_size_max, true, true)) goto end;
/* CacheStorageIndexMax. */
if (!NACP_ADD_U64("CacheStorageIndexMax", (u64)nacp->cache_storage_index_max, true, true)) goto end;
/* Hdcp. */
if (!NACP_ADD_ENUM("Hdcp", nacp->hdcp, nacpGetHdcpString)) goto end;
/* CrashReport. */
if (!NACP_ADD_ENUM("CrashReport", nacp->crash_report, nacpGetCrashReportString)) goto end;
/* CrashScreenshotForProd. */
if (!NACP_ADD_ENUM("CrashScreenshotForProd", nacp->crash_screenshot_for_prod, nacpGetCrashScreenshotForProdString)) goto end;
/* CrashScreenshotForDev. */
if (!NACP_ADD_ENUM("CrashScreenshotForDev", nacp->crash_screenshot_for_dev, nacpGetCrashScreenshotForDevString)) goto end;
/* RuntimeAddOnContentInstall. */
if (!NACP_ADD_ENUM("RuntimeAddOnContentInstall", nacp->runtime_add_on_content_install, nacpGetRuntimeAddOnContentInstallString)) goto end;
/* PlayLogQueryableApplicationId. */
for(i = 0; i < 0x10; i++)
{
if (!nacp->play_log_queryable_application_id[i]) continue;
if (!NACP_ADD_U64("PlayLogQueryableApplicationId", nacp->play_log_queryable_application_id[i], true, true)) goto end;
}
/* PlayLogQueryCapability. */
if (!NACP_ADD_ENUM("PlayLogQueryCapability", nacp->play_log_query_capability, nacpGetPlayLogQueryCapabilityString)) goto end;
/* Repair. */
if (!NACP_ADD_BITFLAG("Repair", &(nacp->repair), sizeof(nacp->repair), NacpRepair_Count, nacpGetRepairString, false)) goto end;
/* ProgramIndex. */
if (!NACP_ADD_U16("ProgramIndex", nacp->program_index, false, false)) goto end;
/* RequiredNetworkServiceLicenseOnLaunch. */
if (!NACP_ADD_BITFLAG("RequiredNetworkServiceLicenseOnLaunch", &(nacp->required_network_service_license_on_launch), sizeof(nacp->required_network_service_license_on_launch), \
NacpRequiredNetworkServiceLicenseOnLaunch_Count, nacpGetRequiredNetworkServiceLicenseOnLaunchString, false)) goto end;
/* NeighborDetectionClientConfiguration. */
ndcc_sgc_available = (ndcc->send_group_configuration.group_id && memcmp(ndcc->send_group_configuration.key, null_key, sizeof(null_key)));
for(i = 0; i < 0x10; i++)
{
ndcc_rgc_available = (ndcc->receivable_group_configurations[i].group_id && memcmp(ndcc->receivable_group_configurations[i].key, null_key, sizeof(null_key)));
if (ndcc_rgc_available) break;
}
if (ndcc_sgc_available || ndcc_rgc_available)
{
if (!NACP_ADD_FMT_STR_T1(" <NeighborDetectionClientConfiguration>\n")) goto end;
/* SendGroupConfiguration. */
utilsGenerateHexStringFromData(key_str, sizeof(key_str), ndcc->send_group_configuration.key, sizeof(ndcc->send_group_configuration.key), false);
if (!NACP_ADD_FMT_STR_T1(" <SendGroupConfiguration>\n" \
" <GroupId>0x%016lx</GroupId>\n" \
" <Key>%s</Key>\n" \
" </SendGroupConfiguration>\n", \
ndcc->send_group_configuration.group_id, \
key_str)) goto end;
/* ReceivableGroupConfiguration. */
for(i = 0; i < 0x10; i++)
{
NacpApplicationNeighborDetectionGroupConfiguration *rgc = &(ndcc->receivable_group_configurations[i]);
utilsGenerateHexStringFromData(key_str, sizeof(key_str), rgc->key, sizeof(rgc->key), false);
if (!NACP_ADD_FMT_STR_T1(" <ReceivableGroupConfiguration>\n" \
" <GroupId>0x%016lx</GroupId>\n" \
" <Key>%s</Key>\n" \
@ -749,10 +749,10 @@ bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 requir
rgc->group_id, \
key_str)) goto end;
}
if (!NACP_ADD_FMT_STR_T1(" </NeighborDetectionClientConfiguration>\n")) goto end;
}
/* JitConfiguration. */
if (!NACP_ADD_FMT_STR_T1(" <Jit>\n" \
" <IsEnabled>%s</IsEnabled>\n" \
@ -760,53 +760,53 @@ bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 requir
" </Jit>\n", \
(nacp->jit_configuration.jit_configuration_flag & NacpJitConfigurationFlag_Enabled) ? "true" : "false", \
nacp->jit_configuration.memory_size)) goto end;
/* History. */
//if (!NACP_ADD_FMT_STR_T1(" <History />\n")) goto end;
/* RuntimeParameterDelivery. */
if (!NACP_ADD_ENUM("RuntimeParameterDelivery", nacp->runtime_parameter_delivery, nacpGetRuntimeParameterDeliveryString)) goto end;
/* AppropriateAgeForChina. */
if (!NACP_ADD_ENUM("AppropriateAgeForChina", nacp->appropriate_age_for_china, nacpGetAppropriateAgeForChina)) goto end;
/* RequiredAddOnContentsSet. */
for(i = 0; i < 0x20; i++)
{
NacpRequiredAddOnContentsSetDescriptor *descriptor = &(raocsbd->descriptors[i]);
if (descriptor->index)
{
raocsbd_available = true;
break;
}
if (descriptor->flag != NacpRequiredAddOnContentsSetDescriptorFlag_Continue) break;
}
if (raocsbd_available)
{
if (!NACP_ADD_FMT_STR_T1(" <RequiredAddOnContentsSet>\n")) goto end;
for(i = 0; i < 0x20; i++)
{
NacpRequiredAddOnContentsSetDescriptor *descriptor = &(raocsbd->descriptors[i]);
if (!descriptor->index) continue;
if (!NACP_ADD_FMT_STR_T1(" <Index>%u</Index>\n", descriptor->index)) goto end;
if (descriptor->flag != NacpRequiredAddOnContentsSetDescriptorFlag_Continue) break;
}
if (!NACP_ADD_FMT_STR_T1(" </RequiredAddOnContentsSet>\n")) goto end;
}
/* PlayReportPermission. */
if (!NACP_ADD_FMT_STR_T1(" <PlayReportPermission>\n" \
" <TargetMarketing>%s</TargetMarketing>\n" \
" </PlayReportPermission>\n", \
(nacp->play_report_permission & NacpPlayReportPermission_TargetMarketing) ? "Allow" : "Deny")) goto end;
/* AccessibleLaunchRequiredVersion. */
for(i = 0; i < 0x8; i++)
{
@ -816,37 +816,37 @@ bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 requir
break;
}
}
if (alrv_available)
{
if (!NACP_ADD_FMT_STR_T1(" <AccessibleLaunchRequiredVersion>\n")) goto end;
for(i = 0; i < 0x8; i++)
{
u64 id = nacp->accessible_launch_required_version.application_id[i];
if (!id) continue;
if (!NACP_ADD_FMT_STR_T1(" <ApplicationId>0x%016lx</ApplicationId>\n", id)) goto end;
}
if (!NACP_ADD_FMT_STR_T1(" </AccessibleLaunchRequiredVersion>\n")) goto end;
} else {
if (!NACP_ADD_FMT_STR_T1(" <AccessibleLaunchRequiredVersion />\n")) goto end;
}
/* UndecidedParameter75b8b. */
if (!NACP_ADD_ENUM("UndecidedParameter75b8b", nacp->undecided_parameter_75b8b, nacpGetUndecidedParameter75b8bString)) goto end;
/* ApplicationId. */
if (!NACP_ADD_U64("ApplicationId", nacp_ctx->nca_ctx->header.program_id, true, true)) goto end;
/* FilterDescriptionFilePath and CompressionFileConfigurationFilePath. */
/*if (!NACP_ADD_FMT_STR_T1(" <FilterDescriptionFilePath />\n" \
" <CompressionFileConfigurationFilePath />\n")) goto end;*/
/* ContentsAvailabilityTransitionPolicy. */
if (!NACP_ADD_ENUM("ContentsAvailabilityTransitionPolicy", nacp->contents_availability_transition_policy, nacpGetContentsAvailabilityTransitionPolicyString)) goto end;
/* LimitedLicenseSettings. */
if (!NACP_ADD_FMT_STR_T1(" <LimitedLicenseSettings>\n" \
" <RuntimeUpgrade>%s</RuntimeUpgrade>\n" \
@ -856,20 +856,20 @@ bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 requir
" </LimitedLicenseSettings>\n", \
nacpGetRuntimeUpgradeString(nacp->runtime_upgrade), \
(nacp->supporting_limited_licenses & NacpSupportingLimitedLicenses_Demo) ? "Demo" : "None")) goto end;
if (!(success = NACP_ADD_FMT_STR_T1("</Application>"))) goto end;
/* Update NACP context. */
nacp_ctx->authoring_tool_xml = xml_buf;
nacp_ctx->authoring_tool_xml_size = strlen(xml_buf);
end:
if (!success)
{
if (xml_buf) free(xml_buf);
LOG_MSG("Failed to generate NACP AuthoringTool XML!");
}
return success;
}
@ -1026,7 +1026,7 @@ static bool nacpAddStringFieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_si
LOG_MSG("Invalid parameters!");
return false;
}
return (*value ? NACP_ADD_FMT_STR_T2(" <%s>%s</%s>\n", tag_name, value, tag_name) : NACP_ADD_FMT_STR_T2(" <%s />\n", tag_name));
}
@ -1037,7 +1037,7 @@ static bool nacpAddEnumFieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size
LOG_MSG("Invalid parameters!");
return false;
}
return NACP_ADD_FMT_STR_T2(" <%s>%s</%s>\n", tag_name, str_func(value), tag_name);
}
@ -1046,14 +1046,14 @@ static bool nacpAddBitflagFieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_s
u8 flag_bitcount = 0, i = 0, count = 0;
const u8 *flag_u8 = (const u8*)flag;
bool success = false, empty_flag = true;
if (!xml_buf || !xml_buf_size || !tag_name || !*tag_name || !flag || !flag_width || (flag_width > 1 && !IS_POWER_OF_TWO(flag_width)) || flag_width > 0x10 || \
(flag_bitcount = (flag_width * 8)) < max_flag_idx || !str_func)
{
LOG_MSG("Invalid parameters!");
return false;
}
for(i = 0; i < flag_width; i++)
{
if (flag_u8[i])
@ -1062,7 +1062,7 @@ static bool nacpAddBitflagFieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_s
break;
}
}
if (!empty_flag)
{
for(i = 0; i < max_flag_idx; i++)
@ -1071,15 +1071,15 @@ static bool nacpAddBitflagFieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_s
if (!NACP_ADD_FMT_STR_T2(" <%s>%s</%s>\n", tag_name, str_func(i), tag_name)) goto end;
count++;
}
/* Edge case for new, unsupported flags. */
if (!count) empty_flag = true;
}
if (empty_flag && allow_empty_str && !NACP_ADD_FMT_STR_T2(" <%s />\n", tag_name)) goto end;
success = true;
end:
return success;
}
@ -1091,9 +1091,9 @@ static bool nacpAddU16FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size,
LOG_MSG("Invalid parameters!");
return false;
}
const char *str = (hex ? (prefix ? " <%s>0x%04x</%s>\n" : " <%s>%04x</%s>\n") : " <%s>%u</%s>\n");
return NACP_ADD_FMT_STR_T2(str, tag_name, value, tag_name);
}
@ -1104,9 +1104,9 @@ static bool nacpAddU32FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size,
LOG_MSG("Invalid parameters!");
return false;
}
const char *str = (hex ? (prefix ? " <%s>0x%08x</%s>\n" : " <%s>%08x</%s>\n") : " <%s>%u</%s>\n");
return NACP_ADD_FMT_STR_T2(str, tag_name, value, tag_name);
}
@ -1117,8 +1117,8 @@ static bool nacpAddU64FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size,
LOG_MSG("Invalid parameters!");
return false;
}
const char *str = (hex ? (prefix ? " <%s>0x%016lx</%s>\n" : " <%s>%016lx</%s>\n") : " <%s>%lu</%s>\n");
return NACP_ADD_FMT_STR_T2(str, tag_name, value, tag_name);
}

File diff suppressed because it is too large Load diff

View file

@ -35,52 +35,52 @@ bool ncaStorageInitializeContext(NcaStorageContext *out, NcaFsSectionContext *nc
LOG_MSG("Invalid parameters!");
return false;
}
bool success = false;
/* Free output context beforehand. */
ncaStorageFreeContext(out);
/* Set initial base storage type. */
out->base_storage_type = NcaStorageBaseStorageType_Regular;
/* Check if a sparse layer is available. */
if (nca_fs_ctx->has_sparse_layer)
{
/* Initialize sparse layer. */
if (!ncaStorageInitializeBucketTreeContext(&(out->sparse_storage), nca_fs_ctx, BucketTreeStorageType_Sparse)) goto end;
/* Set sparse layer's substorage. */
if (!bktrSetRegularSubStorage(out->sparse_storage, nca_fs_ctx)) goto end;
/* Update base storage type. */
out->base_storage_type = NcaStorageBaseStorageType_Sparse;
}
/* Check if both Indirect and AesCtrEx layers are available. */
if (nca_fs_ctx->section_type == NcaFsSectionType_PatchRomFs)
{
/* Initialize AesCtrEx and Indirect layers. */
if (!ncaStorageInitializeBucketTreeContext(&(out->aes_ctr_ex_storage), nca_fs_ctx, BucketTreeStorageType_AesCtrEx) || \
!ncaStorageInitializeBucketTreeContext(&(out->indirect_storage), nca_fs_ctx, BucketTreeStorageType_Indirect)) goto end;
/* Set AesCtrEx layer's substorage. */
if (!bktrSetRegularSubStorage(out->aes_ctr_ex_storage, nca_fs_ctx)) goto end;
/* Set Indirect layer's AesCtrEx substorage. */
/* Base substorage must be manually set at a later time. */
if (!bktrSetBucketTreeSubStorage(out->indirect_storage, out->aes_ctr_ex_storage, 1)) goto end;
/* Update base storage type. */
out->base_storage_type = NcaStorageBaseStorageType_Indirect;
}
/* Check if a compression layer is available. */
if (nca_fs_ctx->has_compression_layer)
{
/* Initialize compression layer. */
if (!ncaStorageInitializeBucketTreeContext(&(out->compressed_storage), nca_fs_ctx, BucketTreeStorageType_Compressed)) goto end;
/* Set compression layer's substorage. */
switch(out->base_storage_type)
{
@ -94,27 +94,27 @@ bool ncaStorageInitializeContext(NcaStorageContext *out, NcaFsSectionContext *nc
if (!bktrSetBucketTreeSubStorage(out->compressed_storage, out->indirect_storage, 0)) goto end;
break;
}
/* Update base storage type. */
out->base_storage_type = NcaStorageBaseStorageType_Compressed;
}
/* Update output context. */
out->nca_fs_ctx = nca_fs_ctx;
/* Update return value. */
success = true;
end:
if (!success) ncaStorageFreeContext(out);
return success;
}
bool ncaStorageSetPatchOriginalSubStorage(NcaStorageContext *patch_ctx, NcaStorageContext *base_ctx)
{
NcaContext *patch_nca_ctx = NULL, *base_nca_ctx = NULL;
if (!ncaStorageIsValidContext(patch_ctx) || !ncaStorageIsValidContext(base_ctx) || patch_ctx->nca_fs_ctx == base_ctx->nca_fs_ctx || \
!(patch_nca_ctx = (NcaContext*)patch_ctx->nca_fs_ctx->nca_ctx) || !(base_nca_ctx = (NcaContext*)base_ctx->nca_fs_ctx->nca_ctx) || \
patch_ctx->nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || base_ctx->nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \
@ -124,9 +124,9 @@ bool ncaStorageSetPatchOriginalSubStorage(NcaStorageContext *patch_ctx, NcaStora
LOG_MSG("Invalid parameters!");
return false;
}
bool success = false;
/* Set original substorage. */
switch(base_ctx->base_storage_type)
{
@ -142,9 +142,9 @@ bool ncaStorageSetPatchOriginalSubStorage(NcaStorageContext *patch_ctx, NcaStora
default:
break;
}
if (!success) LOG_MSG("Failed to set base storage to patch storage!");
return success;
}
@ -155,17 +155,17 @@ bool ncaStorageGetHashTargetExtents(NcaStorageContext *ctx, u64 *out_offset, u64
LOG_MSG("Invalid parameters!");
return false;
}
u64 hash_target_offset = 0, hash_target_size = 0;
bool success = false;
/* Get hash target extents from the NCA FS section. */
if (!ncaGetFsSectionHashTargetExtents(ctx->nca_fs_ctx, &hash_target_offset, &hash_target_size))
{
LOG_MSG("Failed to retrieve NCA FS section's hash target extents!");
goto end;
}
/* Set proper hash target extents. */
switch(ctx->base_storage_type)
{
@ -189,10 +189,10 @@ bool ncaStorageGetHashTargetExtents(NcaStorageContext *ctx, u64 *out_offset, u64
default:
break;
}
/* Update return value. */
success = true;
end:
return success;
}
@ -204,9 +204,9 @@ bool ncaStorageRead(NcaStorageContext *ctx, void *out, u64 read_size, u64 offset
LOG_MSG("Invalid parameters!");
return false;
}
bool success = false;
switch(ctx->base_storage_type)
{
case NcaStorageBaseStorageType_Regular:
@ -224,40 +224,40 @@ bool ncaStorageRead(NcaStorageContext *ctx, void *out, u64 read_size, u64 offset
default:
break;
}
if (!success) LOG_MSG("Failed to read 0x%lX-byte long block from offset 0x%lX in base storage! (type: %u).", read_size, offset, ctx->base_storage_type);
return success;
}
void ncaStorageFreeContext(NcaStorageContext *ctx)
{
if (!ctx) return;
if (ctx->sparse_storage)
{
bktrFreeContext(ctx->sparse_storage);
free(ctx->sparse_storage);
}
if (ctx->aes_ctr_ex_storage)
{
bktrFreeContext(ctx->aes_ctr_ex_storage);
free(ctx->aes_ctr_ex_storage);
}
if (ctx->indirect_storage)
{
bktrFreeContext(ctx->indirect_storage);
free(ctx->indirect_storage);
}
if (ctx->compressed_storage)
{
bktrFreeContext(ctx->compressed_storage);
free(ctx->compressed_storage);
}
memset(ctx, 0, sizeof(NcaStorageContext));
}
@ -268,10 +268,10 @@ static bool ncaStorageInitializeBucketTreeContext(BucketTreeContext **out, NcaFs
LOG_MSG("Invalid parameters!");
return false;
}
BucketTreeContext *bktr_ctx = NULL;
bool success = false;
/* Allocate memory for the Bucket Tree context. */
bktr_ctx = calloc(1, sizeof(BucketTreeContext));
if (!bktr_ctx)
@ -279,7 +279,7 @@ static bool ncaStorageInitializeBucketTreeContext(BucketTreeContext **out, NcaFs
LOG_MSG("Unable to allocate memory for Bucket Tree context! (%u).", storage_type);
goto end;
}
/* Initialize Bucket Tree context. */
success = bktrInitializeContext(bktr_ctx, nca_fs_ctx, storage_type);
if (!success)
@ -287,12 +287,12 @@ static bool ncaStorageInitializeBucketTreeContext(BucketTreeContext **out, NcaFs
LOG_MSG("Failed to initialize Bucket Tree context! (%u).", storage_type);
goto end;
}
/* Update output context pointer. */
*out = bktr_ctx;
end:
if (!success && bktr_ctx) free(bktr_ctx);
return success;
}

View file

@ -29,7 +29,7 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
u64 cur_offset = 0;
bool success = false, dump_meta_header = false, dump_acid_header = false, dump_aci_header = false;
PartitionFileSystemEntry *pfs_entry = NULL;
if (!out || !pfs_ctx || !ncaStorageIsValidContext(&(pfs_ctx->storage_ctx)) || !(nca_ctx = (NcaContext*)pfs_ctx->nca_fs_ctx->nca_ctx) || \
nca_ctx->content_type != NcmContentType_Program || !pfs_ctx->offset || !pfs_ctx->size || !pfs_ctx->is_exefs || \
pfs_ctx->header_size <= sizeof(PartitionFileSystemHeader) || !pfs_ctx->header)
@ -37,26 +37,26 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
LOG_MSG("Invalid parameters!");
return false;
}
/* Free output context beforehand. */
npdmFreeContext(out);
/* Get 'main.npdm' file entry. */
if (!(pfs_entry = pfsGetEntryByName(pfs_ctx, "main.npdm")))
{
LOG_MSG("'main.npdm' entry unavailable in ExeFS!");
goto end;
}
//LOG_MSG("Found 'main.npdm' entry in Program NCA \"%s\".", nca_ctx->content_id_str);
/* Check raw NPDM size. */
if (!pfs_entry->size)
{
LOG_MSG("Invalid raw NPDM size!");
goto end;
}
/* Allocate memory for the raw NPDM data. */
out->raw_data_size = pfs_entry->size;
if (!(out->raw_data = malloc(out->raw_data_size)))
@ -64,25 +64,25 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
LOG_MSG("Failed to allocate memory for the raw NPDM data!");
goto end;
}
/* Read raw NPDM data into memory buffer. */
if (!pfsReadEntryData(pfs_ctx, pfs_entry, out->raw_data, out->raw_data_size, 0))
{
LOG_MSG("Failed to read raw NPDM data!");
goto end;
}
/* Verify meta header. */
out->meta_header = (NpdmMetaHeader*)out->raw_data;
cur_offset += sizeof(NpdmMetaHeader);
if (__builtin_bswap32(out->meta_header->magic) != NPDM_META_MAGIC)
{
LOG_MSG("Invalid meta header magic word! (0x%08X != 0x%08X).", __builtin_bswap32(out->meta_header->magic), __builtin_bswap32(NPDM_META_MAGIC));
dump_meta_header = true;
goto end;
}
if (!out->meta_header->flags.is_64bit_instruction && (out->meta_header->flags.process_address_space == NpdmProcessAddressSpace_AddressSpace64BitOld || \
out->meta_header->flags.process_address_space == NpdmProcessAddressSpace_AddressSpace64Bit))
{
@ -90,49 +90,49 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
dump_meta_header = true;
goto end;
}
if (out->meta_header->main_thread_priority > NPDM_MAIN_THREAD_MAX_PRIORITY)
{
LOG_MSG("Invalid main thread priority! (0x%02X).", out->meta_header->main_thread_priority);
dump_meta_header = true;
goto end;
}
if (out->meta_header->main_thread_core_number > NPDM_MAIN_THREAD_MAX_CORE_NUMBER)
{
LOG_MSG("Invalid main thread core number! (%u).", out->meta_header->main_thread_core_number);
dump_meta_header = true;
goto end;
}
if (out->meta_header->system_resource_size > NPDM_SYSTEM_RESOURCE_MAX_SIZE)
{
LOG_MSG("Invalid system resource size! (0x%08X).", out->meta_header->system_resource_size);
dump_meta_header = true;
goto end;
}
if (!IS_ALIGNED(out->meta_header->main_thread_stack_size, NPDM_MAIN_THREAD_STACK_SIZE_ALIGNMENT))
{
LOG_MSG("Invalid main thread stack size! (0x%08X).", out->meta_header->main_thread_stack_size);
dump_meta_header = true;
goto end;
}
if (out->meta_header->aci_offset < sizeof(NpdmMetaHeader) || out->meta_header->aci_size < sizeof(NpdmAciHeader) || (out->meta_header->aci_offset + out->meta_header->aci_size) > out->raw_data_size)
{
LOG_MSG("Invalid ACI0 offset/size! (0x%08X, 0x%08X).", out->meta_header->aci_offset, out->meta_header->aci_size);
dump_meta_header = true;
goto end;
}
if (out->meta_header->acid_offset < sizeof(NpdmMetaHeader) || out->meta_header->acid_size < sizeof(NpdmAcidHeader) || (out->meta_header->acid_offset + out->meta_header->acid_size) > out->raw_data_size)
{
LOG_MSG("Invalid ACID offset/size! (0x%08X, 0x%08X).", out->meta_header->acid_offset, out->meta_header->acid_size);
dump_meta_header = true;
goto end;
}
if (out->meta_header->aci_offset == out->meta_header->acid_offset || \
(out->meta_header->aci_offset > out->meta_header->acid_offset && out->meta_header->aci_offset < (out->meta_header->acid_offset + out->meta_header->acid_size)) || \
(out->meta_header->acid_offset > out->meta_header->aci_offset && out->meta_header->acid_offset < (out->meta_header->aci_offset + out->meta_header->aci_size)))
@ -141,32 +141,32 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
dump_meta_header = true;
goto end;
}
/* Verify ACID section. */
out->acid_header = (NpdmAcidHeader*)(out->raw_data + out->meta_header->acid_offset);
cur_offset += out->meta_header->acid_size;
if (__builtin_bswap32(out->acid_header->magic) != NPDM_ACID_MAGIC)
{
LOG_MSG("Invalid ACID header magic word! (0x%08X != 0x%08X).", __builtin_bswap32(out->acid_header->magic), __builtin_bswap32(NPDM_ACID_MAGIC));
dump_meta_header = dump_acid_header = true;
goto end;
}
if (out->acid_header->size != (out->meta_header->acid_size - sizeof(out->acid_header->signature)))
{
LOG_MSG("Invalid ACID header size! (0x%08X).", out->acid_header->size);
dump_meta_header = dump_acid_header = true;
goto end;
}
if (out->acid_header->program_id_min > out->acid_header->program_id_max)
{
LOG_MSG("Invalid ACID program ID range! (%016lX - %016lX).", out->acid_header->program_id_min, out->acid_header->program_id_max);
dump_meta_header = dump_acid_header = true;
goto end;
}
if (out->acid_header->fs_access_control_offset < sizeof(NpdmAcidHeader) || out->acid_header->fs_access_control_size < sizeof(NpdmFsAccessControlDescriptor) || \
(out->acid_header->fs_access_control_offset + out->acid_header->fs_access_control_size) > out->meta_header->acid_size)
{
@ -174,9 +174,9 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
dump_meta_header = dump_acid_header = true;
goto end;
}
out->acid_fac_descriptor = (NpdmFsAccessControlDescriptor*)(out->raw_data + out->meta_header->acid_offset + out->acid_header->fs_access_control_offset);
if (out->acid_header->srv_access_control_size)
{
if (out->acid_header->srv_access_control_offset < sizeof(NpdmAcidHeader) || \
@ -186,10 +186,10 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
dump_meta_header = dump_acid_header = true;
goto end;
}
out->acid_sac_descriptor = (NpdmSrvAccessControlDescriptorEntry*)(out->raw_data + out->meta_header->acid_offset + out->acid_header->srv_access_control_offset);
}
if (out->acid_header->kernel_capability_size)
{
if (!IS_ALIGNED(out->acid_header->kernel_capability_size, sizeof(NpdmKernelCapabilityDescriptorEntry)) || \
@ -200,35 +200,35 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
dump_meta_header = dump_acid_header = true;
goto end;
}
out->acid_kc_descriptor = (NpdmKernelCapabilityDescriptorEntry*)(out->raw_data + out->meta_header->acid_offset + out->acid_header->kernel_capability_offset);
}
/* Verify ACI0 section. */
out->aci_header = (NpdmAciHeader*)(out->raw_data + out->meta_header->aci_offset);
cur_offset += out->meta_header->aci_size;
if (__builtin_bswap32(out->aci_header->magic) != NPDM_ACI0_MAGIC)
{
LOG_MSG("Invalid ACI0 header magic word! (0x%08X != 0x%08X).", __builtin_bswap32(out->aci_header->magic), __builtin_bswap32(NPDM_ACI0_MAGIC));
dump_meta_header = dump_acid_header = dump_aci_header = true;
goto end;
}
if (out->aci_header->program_id != nca_ctx->header.program_id)
{
LOG_MSG("ACI0 program ID mismatch! (%016lX != %016lX).", out->aci_header->program_id, nca_ctx->header.program_id);
dump_meta_header = dump_acid_header = dump_aci_header = true;
goto end;
}
if (out->aci_header->program_id < out->acid_header->program_id_min || out->aci_header->program_id > out->acid_header->program_id_max)
{
LOG_MSG("ACI0 program ID out of ACID program ID range! (%016lX, %016lX - %016lX).", out->aci_header->program_id, out->acid_header->program_id_min, out->acid_header->program_id_max);
dump_meta_header = dump_acid_header = dump_aci_header = true;
goto end;
}
if (out->aci_header->fs_access_control_offset < sizeof(NpdmAciHeader) || out->aci_header->fs_access_control_size < sizeof(NpdmFsAccessControlData) || \
(out->aci_header->fs_access_control_offset + out->aci_header->fs_access_control_size) > out->meta_header->aci_size)
{
@ -236,9 +236,9 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
dump_meta_header = dump_acid_header = dump_aci_header = true;
goto end;
}
out->aci_fac_data = (NpdmFsAccessControlData*)(out->raw_data + out->meta_header->aci_offset + out->aci_header->fs_access_control_offset);
if (out->aci_header->srv_access_control_size)
{
if (out->aci_header->srv_access_control_offset < sizeof(NpdmAciHeader) || \
@ -248,10 +248,10 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
dump_meta_header = dump_acid_header = dump_aci_header = true;
goto end;
}
out->aci_sac_descriptor = (NpdmSrvAccessControlDescriptorEntry*)(out->raw_data + out->meta_header->aci_offset + out->aci_header->srv_access_control_offset);
}
if (out->aci_header->kernel_capability_size)
{
if (!IS_ALIGNED(out->aci_header->kernel_capability_size, sizeof(NpdmKernelCapabilityDescriptorEntry)) || \
@ -262,28 +262,28 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
dump_meta_header = dump_acid_header = dump_aci_header = true;
goto end;
}
out->aci_kc_descriptor = (NpdmKernelCapabilityDescriptorEntry*)(out->raw_data + out->meta_header->aci_offset + out->aci_header->kernel_capability_offset);
}
/* Safety check: verify raw NPDM size. */
if (out->raw_data_size < cur_offset)
{
LOG_MSG("Invalid raw NPDM size! (0x%lX < 0x%lX).", out->raw_data_size, cur_offset);
goto end;
}
success = true;
end:
if (!success)
{
if (dump_aci_header) LOG_DATA(out->aci_header, sizeof(NpdmAciHeader), "NPDM ACI0 Header dump:");
if (dump_acid_header) LOG_DATA(out->acid_header, sizeof(NpdmAcidHeader), "NPDM ACID Header dump:");
if (dump_meta_header) LOG_DATA(out->meta_header, sizeof(NpdmMetaHeader), "NPDM Meta Header dump:");
npdmFreeContext(out);
}
return success;
}

View file

@ -34,7 +34,7 @@ bool nsoInitializeContext(NsoContext *out, PartitionFileSystemContext *pfs_ctx,
NcaContext *nca_ctx = NULL;
u8 *rodata_buf = NULL;
bool success = false, dump_nso_header = false;
if (!out || !pfs_ctx || !ncaStorageIsValidContext(&(pfs_ctx->storage_ctx)) || !(nca_ctx = (NcaContext*)pfs_ctx->nca_fs_ctx->nca_ctx) || \
nca_ctx->content_type != NcmContentType_Program || !pfs_ctx->offset || !pfs_ctx->size || !pfs_ctx->is_exefs || \
pfs_ctx->header_size <= sizeof(PartitionFileSystemHeader) || !pfs_ctx->header || !pfs_entry)
@ -42,28 +42,28 @@ bool nsoInitializeContext(NsoContext *out, PartitionFileSystemContext *pfs_ctx,
LOG_MSG("Invalid parameters!");
return false;
}
/* Free output context beforehand. */
nsoFreeContext(out);
/* Update output context. */
out->pfs_ctx = pfs_ctx;
out->pfs_entry = pfs_entry;
/* Get entry filename. */
if (!(out->nso_filename = pfsGetEntryName(pfs_ctx, pfs_entry)) || !*(out->nso_filename))
{
LOG_MSG("Invalid Partition FS entry filename!");
goto end;
}
/* Read NSO header. */
if (!pfsReadEntryData(pfs_ctx, pfs_entry, &(out->nso_header), sizeof(NsoHeader), 0))
{
LOG_MSG("Failed to read NSO \"%s\" header!", out->nso_filename);;
goto end;
}
/* Verify NSO header. */
if (__builtin_bswap32(out->nso_header.magic) != NSO_HEADER_MAGIC)
{
@ -71,7 +71,7 @@ bool nsoInitializeContext(NsoContext *out, PartitionFileSystemContext *pfs_ctx,
dump_nso_header = true;
goto end;
}
if (out->nso_header.text_segment_info.file_offset < sizeof(NsoHeader) || !out->nso_header.text_segment_info.size || \
((out->nso_header.flags & NsoFlags_TextCompress) && (!out->nso_header.text_file_size || out->nso_header.text_file_size > out->nso_header.text_segment_info.size)) || \
(!(out->nso_header.flags & NsoFlags_TextCompress) && out->nso_header.text_file_size != out->nso_header.text_segment_info.size) || \
@ -82,7 +82,7 @@ bool nsoInitializeContext(NsoContext *out, PartitionFileSystemContext *pfs_ctx,
dump_nso_header = true;
goto end;
}
if (out->nso_header.rodata_segment_info.file_offset < sizeof(NsoHeader) || !out->nso_header.rodata_segment_info.size || \
((out->nso_header.flags & NsoFlags_RoCompress) && (!out->nso_header.rodata_file_size || out->nso_header.rodata_file_size > out->nso_header.rodata_segment_info.size)) || \
(!(out->nso_header.flags & NsoFlags_RoCompress) && out->nso_header.rodata_file_size != out->nso_header.rodata_segment_info.size) || \
@ -93,7 +93,7 @@ bool nsoInitializeContext(NsoContext *out, PartitionFileSystemContext *pfs_ctx,
dump_nso_header = true;
goto end;
}
if (out->nso_header.data_segment_info.file_offset < sizeof(NsoHeader) || !out->nso_header.data_segment_info.size || \
((out->nso_header.flags & NsoFlags_DataCompress) && (!out->nso_header.data_file_size || out->nso_header.data_file_size > out->nso_header.data_segment_info.size)) || \
(!(out->nso_header.flags & NsoFlags_DataCompress) && out->nso_header.data_file_size != out->nso_header.data_segment_info.size) || \
@ -104,91 +104,91 @@ bool nsoInitializeContext(NsoContext *out, PartitionFileSystemContext *pfs_ctx,
dump_nso_header = true;
goto end;
}
if (out->nso_header.module_name_size > 1 && (out->nso_header.module_name_offset < sizeof(NsoHeader) || (out->nso_header.module_name_offset + out->nso_header.module_name_size) > pfs_entry->size))
{
LOG_MSG("Invalid module name offset/size for NSO \"%s\"! (0x%08X, 0x%08X).", out->nso_filename, out->nso_header.module_name_offset, out->nso_header.module_name_size);
dump_nso_header = true;
goto end;
}
if (out->nso_header.api_info_section_info.size && (out->nso_header.api_info_section_info.offset + out->nso_header.api_info_section_info.size) > out->nso_header.rodata_segment_info.size)
{
LOG_MSG("Invalid .api_info section offset/size for NSO \"%s\"! (0x%08X, 0x%08X).", out->nso_filename, out->nso_header.api_info_section_info.offset, out->nso_header.api_info_section_info.size);
dump_nso_header = true;
goto end;
}
if (out->nso_header.dynstr_section_info.size && (out->nso_header.dynstr_section_info.offset + out->nso_header.dynstr_section_info.size) > out->nso_header.rodata_segment_info.size)
{
LOG_MSG("Invalid .dynstr section offset/size for NSO \"%s\"! (0x%08X, 0x%08X).", out->nso_filename, out->nso_header.dynstr_section_info.offset, out->nso_header.dynstr_section_info.size);
dump_nso_header = true;
goto end;
}
if (out->nso_header.dynsym_section_info.size && (out->nso_header.dynsym_section_info.offset + out->nso_header.dynsym_section_info.size) > out->nso_header.rodata_segment_info.size)
{
LOG_MSG("Invalid .dynsym section offset/size for NSO \"%s\"! (0x%08X, 0x%08X).", out->nso_filename, out->nso_header.dynsym_section_info.offset, out->nso_header.dynsym_section_info.size);
dump_nso_header = true;
goto end;
}
/* Get module name. */
if (!nsoGetModuleName(out)) goto end;
/* Get .rodata segment. */
if (!(rodata_buf = nsoGetRodataSegment(out))) goto end;
/* Get module info name. */
if (!nsoGetModuleInfoName(out, rodata_buf)) goto end;
/* Get .api_info section data. */
if (!nsoGetSectionFromRodataSegment(out, rodata_buf, (u8**)&(out->rodata_api_info_section), out->nso_header.api_info_section_info.offset, out->nso_header.api_info_section_info.size)) goto end;
out->rodata_api_info_section_size = out->nso_header.api_info_section_info.size;
/* Get .dynstr section data. */
if (!nsoGetSectionFromRodataSegment(out, rodata_buf, (u8**)&(out->rodata_dynstr_section), out->nso_header.dynstr_section_info.offset, out->nso_header.dynstr_section_info.size)) goto end;
out->rodata_dynstr_section_size = out->nso_header.dynstr_section_info.size;
/* Get .dynsym section data. */
if (!nsoGetSectionFromRodataSegment(out, rodata_buf, &(out->rodata_dynsym_section), out->nso_header.dynsym_section_info.offset, out->nso_header.dynsym_section_info.size)) goto end;
out->rodata_dynsym_section_size = out->nso_header.dynsym_section_info.size;
success = true;
end:
if (rodata_buf) free(rodata_buf);
if (!success)
{
if (dump_nso_header) LOG_DATA(&(out->nso_header), sizeof(NsoHeader), "NSO header dump:");
nsoFreeContext(out);
}
return success;
}
static bool nsoGetModuleName(NsoContext *nso_ctx)
{
if (nso_ctx->nso_header.module_name_offset < sizeof(NsoHeader) || nso_ctx->nso_header.module_name_size <= 1) return true;
NsoModuleName module_name = {0};
/* Get module name. */
if (!pfsReadEntryData(nso_ctx->pfs_ctx, nso_ctx->pfs_entry, &module_name, sizeof(NsoModuleName), nso_ctx->nso_header.module_name_offset))
{
LOG_MSG("Failed to read NSO \"%s\" module name length!", nso_ctx->nso_filename);
return false;
}
/* Verify module name length. */
if (module_name.name_length != ((u8)nso_ctx->nso_header.module_name_size - 1))
{
LOG_MSG("NSO \"%s\" module name length mismatch! (0x%02X != 0x%02X).", nso_ctx->nso_filename, module_name.name_length, (u8)nso_ctx->nso_header.module_name_size - 1);
return false;
}
/* Allocate memory for the module name. */
nso_ctx->module_name = calloc(nso_ctx->nso_header.module_name_size, sizeof(char));
if (!nso_ctx->module_name)
@ -196,14 +196,14 @@ static bool nsoGetModuleName(NsoContext *nso_ctx)
LOG_MSG("Failed to allocate memory for NSO \"%s\" module name!", nso_ctx->nso_filename);
return false;
}
/* Read module name string. */
if (!pfsReadEntryData(nso_ctx->pfs_ctx, nso_ctx->pfs_entry, nso_ctx->module_name, module_name.name_length, nso_ctx->nso_header.module_name_offset + 1))
{
LOG_MSG("Failed to read NSO \"%s\" module name string!", nso_ctx->nso_filename);
return false;
}
return true;
}
@ -211,33 +211,33 @@ static u8 *nsoGetRodataSegment(NsoContext *nso_ctx)
{
int lz4_res = 0;
bool compressed = (nso_ctx->nso_header.flags & NsoFlags_RoCompress), verify = (nso_ctx->nso_header.flags & NsoFlags_RoHash);
u8 *rodata_buf = NULL;
u64 rodata_buf_size = (compressed ? LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(nso_ctx->nso_header.rodata_segment_info.size) : nso_ctx->nso_header.rodata_segment_info.size);
u8 *rodata_read_ptr = NULL;
u64 rodata_read_size = (compressed ? nso_ctx->nso_header.rodata_file_size : nso_ctx->nso_header.rodata_segment_info.size);
u8 rodata_hash[SHA256_HASH_SIZE] = {0};
bool success = false;
/* Allocate memory for the .rodata buffer. */
if (!(rodata_buf = calloc(rodata_buf_size, sizeof(u8))))
{
LOG_MSG("Failed to allocate 0x%lX bytes for the .rodata segment in NSO \"%s\"!", rodata_buf_size, nso_ctx->nso_filename);
return NULL;
}
rodata_read_ptr = (compressed ? (rodata_buf + (rodata_buf_size - nso_ctx->nso_header.rodata_file_size)) : rodata_buf);
/* Read .rodata segment data. */
if (!pfsReadEntryData(nso_ctx->pfs_ctx, nso_ctx->pfs_entry, rodata_read_ptr, rodata_read_size, nso_ctx->nso_header.rodata_segment_info.file_offset))
{
LOG_MSG("Failed to read .rodata segment in NRO \"%s\"!", nso_ctx->nso_filename);
goto end;
}
if (compressed)
{
/* Decompress .rodata segment in-place. */
@ -248,7 +248,7 @@ static u8 *nsoGetRodataSegment(NsoContext *nso_ctx)
goto end;
}
}
if (verify)
{
/* Verify .rodata segment hash. */
@ -259,16 +259,16 @@ static u8 *nsoGetRodataSegment(NsoContext *nso_ctx)
goto end;
}
}
success = true;
end:
if (!success && rodata_buf)
{
free(rodata_buf);
rodata_buf = NULL;
}
return rodata_buf;
}
@ -276,7 +276,7 @@ static bool nsoGetModuleInfoName(NsoContext *nso_ctx, u8 *rodata_buf)
{
NsoModuleInfo *module_info = (NsoModuleInfo*)(rodata_buf + 0x4);
if (!module_info->name_length) return true;
/* Allocate memory for the module info name. */
nso_ctx->module_info_name = calloc(module_info->name_length + 1, sizeof(char));
if (!nso_ctx->module_info_name)
@ -284,26 +284,26 @@ static bool nsoGetModuleInfoName(NsoContext *nso_ctx, u8 *rodata_buf)
LOG_MSG("Failed to allocate memory for NSO \"%s\" module info name!", nso_ctx->nso_filename);
return false;
}
/* Copy module info name. */
sprintf(nso_ctx->module_info_name, "%.*s", (int)module_info->name_length, module_info->name);
return true;
}
static bool nsoGetSectionFromRodataSegment(NsoContext *nso_ctx, u8 *rodata_buf, u8 **section_ptr, u64 section_offset, u64 section_size)
{
if (!section_size || (section_offset + section_size) > nso_ctx->nso_header.rodata_segment_info.size) return true;
/* Allocate memory for the desired .rodata section. */
if (!(*section_ptr = malloc(section_size)))
{
LOG_MSG("Failed to allocate 0x%lX bytes for section at .rodata offset 0x%lX in NSO \"%s\"!", section_size, section_offset, nso_ctx->nso_filename);
return false;
}
/* Copy .rodata section data. */
memcpy(*section_ptr, rodata_buf + section_offset, section_size);
return true;
}

View file

@ -39,31 +39,31 @@ bool bfsarInitialize(void)
bool use_root = true;
const char *launch_path = utilsGetLaunchPath();
char *ptr1 = NULL, *ptr2 = NULL;
TitleInfo *title_info = NULL;
NcaContext *nca_ctx = NULL;
RomFileSystemContext romfs_ctx = {0};
RomFileSystemFileEntry *romfs_file_entry = NULL;
FILE *bfsar_file = NULL;
u8 *bfsar_data = NULL;
size_t bfsar_size = 0, wr = 0;
bool ret = false;
SCOPED_LOCK(&g_bfsarMutex)
{
ret = g_bfsarInterfaceInit;
if (ret) break;
/* Generate BFSAR file path. */
if (launch_path)
{
ptr1 = strchr(launch_path, '/');
ptr2 = strrchr(launch_path, '/');
if (ptr1 && ptr2 && ptr1 != ptr2)
{
/* Create BFSAR file in the current working directory. */
@ -71,12 +71,12 @@ bool bfsarInitialize(void)
use_root = false;
}
}
/* Create BFSAR file in the SD card root directory. */
if (use_root) sprintf(g_bfsarPath, "/" BFSAR_FILENAME);
LOG_MSG("BFSAR path: \"%s\".", g_bfsarPath);
/* Check if the BFSAR file is already available and not empty. */
bfsar_file = fopen(g_bfsarPath, "rb");
if (bfsar_file)
@ -89,14 +89,14 @@ bool bfsarInitialize(void)
break;
}
}
/* Get title info. */
if (!(title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, QLAUNCH_TID)))
{
LOG_MSG("Failed to get title info for qlaunch!");
break;
}
/* Allocate memory for a temporary NCA context. */
nca_ctx = calloc(1, sizeof(NcaContext));
if (!nca_ctx)
@ -104,28 +104,28 @@ bool bfsarInitialize(void)
LOG_MSG("Failed to allocate memory for temporary NCA context!");
break;
}
/* Initialize NCA context. */
if (!ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Program, 0), title_info->version.value, NULL))
{
LOG_MSG("Failed to initialize qlaunch Program NCA context!");
break;
}
/* Initialize RomFS context. */
if (!romfsInitializeContext(&romfs_ctx, &(nca_ctx->fs_ctx[1]), NULL))
{
LOG_MSG("Failed to initialize RomFS context for qlaunch Program NCA!");
break;
}
/* Get RomFS file entry. */
if (!(romfs_file_entry = romfsGetFileEntryByPath(&romfs_ctx, BFSAR_ROMFS_PATH)))
{
LOG_MSG("Failed to retrieve RomFS file entry for \"" BFSAR_ROMFS_PATH "\"!");
break;
}
/* Check file size. */
bfsar_size = romfs_file_entry->size;
if (!bfsar_size)
@ -133,21 +133,21 @@ bool bfsarInitialize(void)
LOG_MSG("File size for qlaunch's \"" BFSAR_ROMFS_PATH "\" is zero!");
break;
}
/* Allocate memory for BFSAR data. */
if (!(bfsar_data = malloc(bfsar_size)))
{
LOG_MSG("Failed to allocate 0x%lX bytes for qlaunch's \"" BFSAR_ROMFS_PATH "\"!", bfsar_size);
break;
}
/* Read BFSAR data. */
if (!romfsReadFileEntryData(&romfs_ctx, romfs_file_entry, bfsar_data, bfsar_size, 0))
{
LOG_MSG("Failed to read 0x%lX bytes long \"" BFSAR_ROMFS_PATH "\" from qlaunch!", bfsar_size);
break;
}
/* Create BFSAR file. */
bfsar_file = fopen(g_bfsarPath, "wb");
if (!bfsar_file)
@ -155,7 +155,7 @@ bool bfsarInitialize(void)
LOG_MSG("Failed to open \"%s\" for writing!", g_bfsarPath);
break;
}
/* Write BFSAR data. */
wr = fwrite(bfsar_data, 1, bfsar_size, bfsar_file);
if (wr != bfsar_size)
@ -163,27 +163,27 @@ bool bfsarInitialize(void)
LOG_MSG("Failed to write 0x%lX bytes block to \"%s\"!", bfsar_size, g_bfsarPath);
break;
}
/* Update flags. */
ret = g_bfsarInterfaceInit = true;
}
if (bfsar_file)
{
fclose(bfsar_file);
/* Commit SD card filesystem changes. */
utilsCommitSdCardFileSystemChanges();
}
if (bfsar_data) free(bfsar_data);
romfsFreeContext(&romfs_ctx);
if (nca_ctx) free(nca_ctx);
titleFreeTitleInfo(&title_info);
return ret;
}
@ -200,11 +200,11 @@ void bfsarExit(void)
const char *bfsarGetFilePath(void)
{
const char *ret = NULL;
SCOPED_TRY_LOCK(&g_bfsarMutex)
{
if (g_bfsarInterfaceInit) ret = (const char*)g_bfsarPath;
}
return ret;
}

View file

@ -48,14 +48,14 @@ struct json_object *jsonParseFromString(const char *str, size_t size)
LOG_MSG("Invalid parameters!");
return false;
}
/* Calculate string size if it wasn't provided. */
if (!size) size = (strlen(str) + 1);
struct json_tokener *tok = NULL;
struct json_object *obj = NULL;
enum json_tokener_error jerr = json_tokener_success;
/* Allocate tokener. */
tok = json_tokener_new();
if (!tok)
@ -63,23 +63,23 @@ struct json_object *jsonParseFromString(const char *str, size_t size)
LOG_MSG("json_tokener_new failed!");
goto end;
}
/* Parse JSON buffer. */
obj = json_tokener_parse_ex(tok, str, (int)size);
if ((jerr = json_tokener_get_error(tok)) != json_tokener_success)
{
LOG_MSG("json_tokener_parse_ex failed! Reason: \"%s\".", json_tokener_error_desc(jerr));
if (obj)
{
json_object_put(obj);
obj = NULL;
}
}
end:
if (tok) json_tokener_free(tok);
return obj;
}
@ -88,20 +88,20 @@ struct json_object *jsonGetObjectByPath(const struct json_object *obj, const cha
const struct json_object *parent_obj = obj;
struct json_object *child_obj = NULL;
char *path_dup = NULL, *pch = NULL, *state = NULL, *prev_pch = NULL;
if (!jsonValidateObject(obj) || !path || !*path)
{
LOG_MSG("Invalid parameters!");
return NULL;
}
/* Duplicate path to avoid problems with strtok_r(). */
if (!(path_dup = strdup(path)))
{
LOG_MSG("Unable to duplicate input path! (\"%s\").", path);
return NULL;
}
/* Tokenize input path. */
pch = strtok_r(path_dup, "/", &state);
if (!pch)
@ -109,11 +109,11 @@ struct json_object *jsonGetObjectByPath(const struct json_object *obj, const cha
LOG_MSG("Failed to tokenize input path! (\"%s\").", path);
goto end;
}
while(pch)
{
prev_pch = pch;
pch = strtok_r(NULL, "/", &state);
if (pch || !out_last_element)
{
@ -123,7 +123,7 @@ struct json_object *jsonGetObjectByPath(const struct json_object *obj, const cha
LOG_MSG("Failed to retrieve JSON object by key for \"%s\"! (\"%s\").", prev_pch, path);
break;
}
/* Update parent and child pointers if we can still proceed. */
if (pch)
{
@ -134,7 +134,7 @@ struct json_object *jsonGetObjectByPath(const struct json_object *obj, const cha
/* No additional path elements can be found + the user wants the string for the last path element. */
/* Let's start by setting the last parent object as the return value. */
child_obj = (struct json_object*)parent_obj; /* Drop the const. */
/* Duplicate last path element string. */
*out_last_element = strdup(prev_pch);
if (!*out_last_element)
@ -144,10 +144,10 @@ struct json_object *jsonGetObjectByPath(const struct json_object *obj, const cha
}
}
}
end:
if (path_dup) free(path_dup);
return child_obj;
}
@ -156,11 +156,11 @@ void jsonLogLastError(void)
size_t str_len = 0;
char *str = (char*)json_util_get_last_err(); /* Drop the const. */
if (!str || !(str_len = strlen(str))) return;
/* Remove line breaks. */
if (str[str_len - 1] == '\n') str[--str_len] = '\0';
if (str[str_len - 1] == '\r') str[--str_len] = '\0';
/* Log error message. */
LOG_MSG("%s", str);
}
@ -190,11 +190,11 @@ bool jsonSetArray(const struct json_object *obj, const char *path, struct json_o
LOG_MSG("Invalid parameters!");
return false;
}
bool ret = false;
struct json_object *parent_obj = NULL;
char *key = NULL;
/* Get parent JSON object. */
parent_obj = jsonGetObjectByPath(obj, path, &key);
if (!parent_obj)
@ -202,19 +202,19 @@ bool jsonSetArray(const struct json_object *obj, const char *path, struct json_o
LOG_MSG("Failed to retrieve parent JSON object! (\"%s\").", path);
return false;
}
/* Set new JSON array. */
if (json_object_object_add(parent_obj, key, value) != 0)
{
LOG_MSG("json_object_object_add failed! (\"%s\").", path);
goto end;
}
/* Update return value. */
ret = true;
end:
if (key) free(key);
return ret;
}

View file

@ -61,64 +61,64 @@ __attribute__((format(printf, 2, 3))) void logWriteFormattedStringToLogFile(cons
__attribute__((format(printf, 4, 5))) void logWriteFormattedStringToBuffer(char **dst, size_t *dst_size, const char *func_name, const char *fmt, ...)
{
if (!dst || !dst_size || (!*dst && *dst_size) || (*dst && !*dst_size) || !func_name || !*func_name || !fmt || !*fmt) return;
va_list args;
int str1_len = 0, str2_len = 0;
size_t log_str_len = 0;
char *dst_ptr = *dst, *tmp_str = NULL;
size_t dst_cur_size = *dst_size, dst_str_len = (dst_ptr ? strlen(dst_ptr) : 0);
struct tm ts = {0};
struct timespec now = {0};
if (dst_str_len >= dst_cur_size) return;
va_start(args, fmt);
/* Get current time with nanosecond precision. */
clock_gettime(CLOCK_REALTIME, &now);
/* Get local time. */
localtime_r(&(now.tv_sec), &ts);
ts.tm_year += 1900;
ts.tm_mon++;
/* Get formatted string length. */
str1_len = snprintf(NULL, 0, g_logStrFormat, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, now.tv_nsec, func_name);
if (str1_len <= 0) goto end;
str2_len = vsnprintf(NULL, 0, fmt, args);
if (str2_len <= 0) goto end;
log_str_len = (size_t)(str1_len + str2_len + 3);
if (!dst_cur_size || log_str_len > (dst_cur_size - dst_str_len))
{
/* Update buffer size. */
dst_cur_size = (dst_str_len + log_str_len);
/* Reallocate buffer. */
tmp_str = realloc(dst_ptr, dst_cur_size);
if (!tmp_str) goto end;
dst_ptr = tmp_str;
tmp_str = NULL;
/* Clear allocated area. */
memset(dst_ptr + dst_str_len, 0, log_str_len);
/* Update pointers. */
*dst = dst_ptr;
*dst_size = dst_cur_size;
}
/* Generate formatted string. */
sprintf(dst_ptr + dst_str_len, g_logStrFormat, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, now.tv_nsec, func_name);
vsprintf(dst_ptr + dst_str_len + (size_t)str1_len, fmt, args);
strcat(dst_ptr, CRLF);
end:
va_end(args);
}
@ -126,30 +126,30 @@ end:
__attribute__((format(printf, 4, 5))) void logWriteBinaryDataToLogFile(const void *data, size_t data_size, const char *func_name, const char *fmt, ...)
{
if (!data || !data_size || !func_name || !*func_name || !fmt || !*fmt) return;
va_list args;
size_t data_str_size = ((data_size * 2) + 3);
char *data_str = NULL;
/* Allocate memory for the hex string representation of the provided binary data. */
data_str = calloc(data_str_size, sizeof(char));
if (!data_str) goto end;
/* Generate hex string representation. */
utilsGenerateHexStringFromData(data_str, data_str_size, data, data_size, true);
strcat(data_str, CRLF);
SCOPED_LOCK(&g_logMutex)
{
/* Write formatted string. */
va_start(args, fmt);
_logWriteFormattedStringToLogFile(false, func_name, fmt, args);
va_end(args);
/* Write hex string representation. */
_logWriteStringToLogFile(data_str);
}
end:
if (data_str) free(data_str);
}
@ -165,24 +165,24 @@ void logCloseLogFile(void)
{
/* Flush log buffer. */
_logFlushLogFile();
/* Close logfile. */
if (serviceIsActive(&(g_logFile.s)))
{
fsFileClose(&g_logFile);
memset(&g_logFile, 0, sizeof(FsFile));
/* Commit SD card filesystem changes. */
utilsCommitSdCardFileSystemChanges();
}
/* Free log buffer. */
if (g_logBuffer)
{
free(g_logBuffer);
g_logBuffer = NULL;
}
/* Reset logfile offset. */
g_logFileOffset = 0;
}
@ -199,7 +199,7 @@ void logGetLastMessage(char *dst, size_t dst_size)
void logControlMutex(bool lock)
{
bool locked = mutexIsLockedByCurrentThread(&g_logMutex);
if (!locked && lock)
{
mutexLock(&g_logMutex);
@ -214,10 +214,10 @@ static void _logWriteStringToLogFile(const char *src)
{
/* Make sure we have allocated memory for the log buffer and opened the logfile. */
if (!src || !*src || !logAllocateLogBuffer() || !logOpenLogFile()) return;
Result rc = 0;
size_t src_len = strlen(src), tmp_len = 0;
/* Check if the formatted string length is lower than the log buffer size. */
if (src_len < LOG_BUF_SIZE)
{
@ -227,7 +227,7 @@ static void _logWriteStringToLogFile(const char *src)
_logFlushLogFile();
if (g_logBufferLength) return;
}
/* Copy string into the log buffer. */
strcpy(g_logBuffer + g_logBufferLength, src);
g_logBufferLength += src_len;
@ -235,18 +235,18 @@ static void _logWriteStringToLogFile(const char *src)
/* Flush log buffer. */
_logFlushLogFile();
if (g_logBufferLength) return;
/* Write string data until it no longer exceeds the log buffer size. */
while(src_len >= LOG_BUF_SIZE)
{
rc = fsFileWrite(&g_logFile, g_logFileOffset, src + tmp_len, LOG_BUF_SIZE, FsWriteOption_Flush);
if (R_FAILED(rc)) return;
g_logFileOffset += LOG_BUF_SIZE;
tmp_len += LOG_BUF_SIZE;
src_len -= LOG_BUF_SIZE;
}
/* Copy any remaining data from the string into the log buffer. */
if (src_len)
{
@ -254,7 +254,7 @@ static void _logWriteStringToLogFile(const char *src)
g_logBufferLength = src_len;
}
}
#if LOG_FORCE_FLUSH == 1
/* Flush log buffer. */
_logFlushLogFile();
@ -265,35 +265,35 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name,
{
/* Make sure we have allocated memory for the log buffer and opened the logfile. */
if (!func_name || !*func_name || !fmt || !*fmt || !logAllocateLogBuffer() || !logOpenLogFile()) return;
Result rc = 0;
int str1_len = 0, str2_len = 0;
size_t log_str_len = 0;
char *tmp_str = NULL;
size_t tmp_len = 0;
struct tm ts = {0};
struct timespec now = {0};
/* Get current time with nanosecond precision. */
clock_gettime(CLOCK_REALTIME, &now);
/* Get local time. */
localtime_r(&(now.tv_sec), &ts);
ts.tm_year += 1900;
ts.tm_mon++;
/* Get formatted string length. */
str1_len = snprintf(NULL, 0, g_logStrFormat, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, now.tv_nsec, func_name);
if (str1_len <= 0) return;
str2_len = vsnprintf(NULL, 0, fmt, args);
if (str2_len <= 0) return;
log_str_len = (size_t)(str1_len + str2_len + 2);
/* Save log message to our global stack buffer (if needed). */
if (save)
{
@ -303,10 +303,10 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name,
sprintf(g_lastLogMsg, "%s: ", func_name);
vsprintf(g_lastLogMsg + tmp_len, fmt, args);
}
tmp_len = 0;
}
/* Check if the formatted string length is less than the log buffer size. */
if (log_str_len < LOG_BUF_SIZE)
{
@ -316,7 +316,7 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name,
_logFlushLogFile();
if (g_logBufferLength) return;
}
/* Nice and easy string formatting using the log buffer. */
sprintf(g_logBuffer + g_logBufferLength, g_logStrFormat, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, now.tv_nsec, func_name);
vsprintf(g_logBuffer + g_logBufferLength + (size_t)str1_len, fmt, args);
@ -326,27 +326,27 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name,
/* Flush log buffer. */
_logFlushLogFile();
if (g_logBufferLength) return;
/* Allocate memory for a temporary buffer. This will hold the formatted string. */
tmp_str = calloc(log_str_len + 1, sizeof(char));
if (!tmp_str) return;
/* Generate formatted string. */
sprintf(tmp_str, g_logStrFormat, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, now.tv_nsec, func_name);
vsprintf(tmp_str + (size_t)str1_len, fmt, args);
strcat(tmp_str, CRLF);
/* Write formatted string data until it no longer exceeds the log buffer size. */
while(log_str_len >= LOG_BUF_SIZE)
{
rc = fsFileWrite(&g_logFile, g_logFileOffset, tmp_str + tmp_len, LOG_BUF_SIZE, FsWriteOption_Flush);
if (R_FAILED(rc)) goto end;
g_logFileOffset += LOG_BUF_SIZE;
tmp_len += LOG_BUF_SIZE;
log_str_len -= LOG_BUF_SIZE;
}
/* Copy any remaining data from the formatted string into the log buffer. */
if (log_str_len)
{
@ -354,12 +354,12 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name,
g_logBufferLength = log_str_len;
}
}
#if LOG_FORCE_FLUSH == 1
/* Flush log buffer. */
_logFlushLogFile();
#endif
end:
if (tmp_str) free(tmp_str);
}
@ -367,7 +367,7 @@ end:
static void _logFlushLogFile(void)
{
if (!serviceIsActive(&(g_logFile.s)) || !g_logBuffer || !g_logBufferLength) return;
/* Write log buffer contents and flush the written data right away. */
Result rc = fsFileWrite(&g_logFile, g_logFileOffset, g_logBuffer, g_logBufferLength, FsWriteOption_Flush);
if (R_SUCCEEDED(rc))
@ -389,22 +389,22 @@ static bool logAllocateLogBuffer(void)
static bool logOpenLogFile(void)
{
if (serviceIsActive(&(g_logFile.s))) return true;
Result rc = 0;
bool use_root = true;
const char *launch_path = utilsGetLaunchPath();
char path[FS_MAX_PATH] = {0}, *ptr1 = NULL, *ptr2 = NULL;
/* Get SD card FsFileSystem object. */
FsFileSystem *sdmc_fs = utilsGetSdCardFileSystemObject();
if (!sdmc_fs) return false;
/* Generate logfile path. */
if (launch_path)
{
ptr1 = strchr(launch_path, '/');
ptr2 = strrchr(launch_path, '/');
if (ptr1 && ptr2 && ptr1 != ptr2)
{
/* Create logfile in the current working directory. */
@ -412,13 +412,13 @@ static bool logOpenLogFile(void)
use_root = false;
}
}
/* Create logfile in the SD card root directory. */
if (use_root) sprintf(path, "/" LOG_FILE_NAME);
/* Create file. This will fail if the logfile exists, so we don't check its return value. */
fsFsCreateFile(sdmc_fs, path, 0, 0);
/* Open file. */
rc = fsFsOpenFile(sdmc_fs, path, FsOpenMode_Write | FsOpenMode_Append, &g_logFile);
if (R_SUCCEEDED(rc))
@ -438,6 +438,6 @@ static bool logOpenLogFile(void)
fsFileClose(&g_logFile);
}
}
return R_SUCCEEDED(rc);
}

File diff suppressed because it is too large Load diff

View file

@ -29,12 +29,12 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *
{
NcaContext *nca_ctx = NULL;
u32 magic = 0;
PartitionFileSystemHeader pfs_header = {0};
PartitionFileSystemEntry *main_npdm_entry = NULL;
bool success = false, dump_fs_header = false;
if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || nca_fs_ctx->has_sparse_layer || nca_fs_ctx->section_type != NcaFsSectionType_PartitionFs || \
(nca_fs_ctx->hash_type != NcaHashType_HierarchicalSha256 && nca_fs_ctx->hash_type != NcaHashType_HierarchicalSha3256) || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || \
(nca_ctx->rights_id_available && !nca_ctx->titlekey_retrieved))
@ -42,10 +42,10 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *
LOG_MSG("Invalid parameters!");
return false;
}
/* Free output context beforehand. */
pfsFreeContext(out);
/* Initialize NCA storage context. */
NcaStorageContext *storage_ctx = &(out->storage_ctx);
if (!ncaStorageInitializeContext(storage_ctx, nca_fs_ctx))
@ -53,23 +53,23 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *
LOG_MSG("Failed to initialize NCA storage context!");
goto end;
}
out->nca_fs_ctx = storage_ctx->nca_fs_ctx;
/* Get Partition FS offset and size. */
if (!ncaStorageGetHashTargetExtents(storage_ctx, &(out->offset), &(out->size)))
{
LOG_MSG("Failed to get target hash layer extents!");
goto end;
}
/* Read partial Partition FS header. */
if (!ncaStorageRead(storage_ctx, &pfs_header, sizeof(PartitionFileSystemHeader), out->offset))
{
LOG_MSG("Failed to read partial Partition FS header!");
goto end;
}
magic = __builtin_bswap32(pfs_header.magic);
if (magic != PFS0_MAGIC)
{
@ -77,17 +77,17 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *
dump_fs_header = true;
goto end;
}
if (!pfs_header.entry_count || !pfs_header.name_table_size)
{
LOG_MSG("Invalid Partition FS entry count / name table size!");
dump_fs_header = true;
goto end;
}
/* Calculate full Partition FS header size. */
out->header_size = (sizeof(PartitionFileSystemHeader) + (pfs_header.entry_count * sizeof(PartitionFileSystemEntry)) + pfs_header.name_table_size);
/* Allocate memory for the full Partition FS header. */
out->header = calloc(out->header_size, sizeof(u8));
if (!out->header)
@ -95,29 +95,29 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *
LOG_MSG("Unable to allocate 0x%lX bytes buffer for the full Partition FS header!", out->header_size);
goto end;
}
/* Read full Partition FS header. */
if (!ncaStorageRead(storage_ctx, out->header, out->header_size, out->offset))
{
LOG_MSG("Failed to read full Partition FS header!");
goto end;
}
/* Check if we're dealing with an ExeFS section. */
if ((main_npdm_entry = pfsGetEntryByName(out, "main.npdm")) != NULL && pfsReadEntryData(out, main_npdm_entry, &magic, sizeof(u32), 0) && \
__builtin_bswap32(magic) == NPDM_META_MAGIC) out->is_exefs = true;
/* Update flag. */
success = true;
end:
if (!success)
{
if (dump_fs_header) LOG_DATA(&pfs_header, sizeof(PartitionFileSystemHeader), "Partition FS header dump:");
pfsFreeContext(out);
}
return success;
}
@ -128,14 +128,14 @@ bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_s
LOG_MSG("Invalid parameters!");
return false;
}
/* Read partition data. */
if (!ncaStorageRead(&(ctx->storage_ctx), out, read_size, ctx->offset + offset))
{
LOG_MSG("Failed to read Partition FS data!");
return false;
}
return true;
}
@ -146,14 +146,14 @@ bool pfsReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry
LOG_MSG("Invalid parameters!");
return false;
}
/* Read entry data. */
if (!pfsReadPartitionData(ctx, out, read_size, ctx->header_size + fs_entry->offset + offset))
{
LOG_MSG("Failed to read Partition FS entry data!");
return false;
}
return true;
}
@ -162,15 +162,15 @@ bool pfsGetEntryIndexByName(PartitionFileSystemContext *ctx, const char *name, u
PartitionFileSystemEntry *fs_entry = NULL;
u32 entry_count = pfsGetEntryCount(ctx), name_table_size = 0;
char *name_table = pfsGetNameTable(ctx);
if (!entry_count || !name_table || !name || !*name || !out_idx)
{
LOG_MSG("Invalid parameters!");
return false;
}
name_table_size = ((PartitionFileSystemHeader*)ctx->header)->name_table_size;
for(u32 i = 0; i < entry_count; i++)
{
if (!(fs_entry = pfsGetEntryByIndex(ctx, i)))
@ -178,23 +178,23 @@ bool pfsGetEntryIndexByName(PartitionFileSystemContext *ctx, const char *name, u
LOG_MSG("Failed to retrieve Partition FS entry #%u!", i);
return false;
}
if (fs_entry->name_offset >= name_table_size)
{
LOG_MSG("Name offset from Partition FS entry #%u exceeds name table size!", i);
return false;
}
if (!strcmp(name_table + fs_entry->name_offset, name))
{
*out_idx = i;
return true;
}
}
/* Only log error if we're not dealing with a NPDM. */
if (strcmp(name, "main.npdm") != 0) LOG_MSG("Unable to find Partition FS entry \"%s\"!", name);
return false;
}
@ -203,13 +203,13 @@ bool pfsGetTotalDataSize(PartitionFileSystemContext *ctx, u64 *out_size)
u64 total_size = 0;
u32 entry_count = pfsGetEntryCount(ctx);
PartitionFileSystemEntry *fs_entry = NULL;
if (!entry_count || !out_size)
{
LOG_MSG("Invalid parameters!");
return false;
}
for(u32 i = 0; i < entry_count; i++)
{
if (!(fs_entry = pfsGetEntryByIndex(ctx, i)))
@ -217,12 +217,12 @@ bool pfsGetTotalDataSize(PartitionFileSystemContext *ctx, u64 *out_size)
LOG_MSG("Failed to retrieve Partition FS entry #%u!", i);
return false;
}
total_size += fs_entry->size;
}
*out_size = total_size;
return true;
}
@ -234,15 +234,15 @@ bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemE
LOG_MSG("Invalid parameters!");
return false;
}
u64 partition_offset = (ctx->header_size + fs_entry->offset + data_offset);
if (!ncaGenerateHierarchicalSha256Patch(ctx->nca_fs_ctx, data, data_size, partition_offset, out))
{
LOG_MSG("Failed to generate 0x%lX bytes HierarchicalSha256 patch at offset 0x%lX for Partition FS entry!", data_size, partition_offset);
return false;
}
return true;
}
@ -253,56 +253,56 @@ bool pfsAddEntryInformationToFileContext(PartitionFileSystemFileContext *ctx, co
LOG_MSG("Invalid parameters!");
return false;
}
PartitionFileSystemHeader *header = &(ctx->header);
PartitionFileSystemEntry *tmp_pfs_entries = NULL, *cur_pfs_entry = NULL, *prev_pfs_entry = NULL;
u64 tmp_pfs_entries_size = ((header->entry_count + 1) * sizeof(PartitionFileSystemEntry));
char *tmp_name_table = NULL;
u32 tmp_name_table_size = (header->name_table_size + strlen(entry_name) + 1);
/* Reallocate Partition FS entries. */
if (!(tmp_pfs_entries = realloc(ctx->entries, tmp_pfs_entries_size)))
{
LOG_MSG("Failed to reallocate Partition FS entries!");
return false;
}
ctx->entries = tmp_pfs_entries;
tmp_pfs_entries = NULL;
/* Update Partition FS entry information. */
cur_pfs_entry = &(ctx->entries[header->entry_count]);
prev_pfs_entry = (header->entry_count ? &(ctx->entries[header->entry_count - 1]) : NULL);
memset(cur_pfs_entry, 0, sizeof(PartitionFileSystemEntry));
cur_pfs_entry->offset = (prev_pfs_entry ? (prev_pfs_entry->offset + prev_pfs_entry->size) : 0);
cur_pfs_entry->size = entry_size;
cur_pfs_entry->name_offset = header->name_table_size;
/* Reallocate Partition FS name table. */
if (!(tmp_name_table = realloc(ctx->name_table, tmp_name_table_size)))
{
LOG_MSG("Failed to reallocate Partition FS name table!");
return false;
}
ctx->name_table = tmp_name_table;
tmp_name_table = NULL;
/* Update Partition FS name table. */
sprintf(ctx->name_table + header->name_table_size, "%s", entry_name);
header->name_table_size = tmp_name_table_size;
/* Update output entry index. */
if (out_entry_idx) *out_entry_idx = header->entry_count;
/* Update Partition FS entry count, name table size and data size. */
header->entry_count++;
ctx->fs_size += entry_size;
return true;
}
@ -313,21 +313,21 @@ bool pfsUpdateEntryNameFromFileContext(PartitionFileSystemFileContext *ctx, u32
LOG_MSG("Invalid parameters!");
return false;
}
PartitionFileSystemEntry *pfs_entry = &(ctx->entries[entry_idx]);
char *name_table_entry = (ctx->name_table + pfs_entry->name_offset);
size_t new_entry_name_len = strlen(new_entry_name);
size_t cur_entry_name_len = strlen(name_table_entry);
if (new_entry_name_len > cur_entry_name_len)
{
LOG_MSG("New entry name length exceeds previous entry name length! (0x%lX > 0x%lX).", new_entry_name_len, cur_entry_name_len);
return false;
}
memcpy(name_table_entry, new_entry_name, new_entry_name_len);
return true;
}
@ -338,45 +338,45 @@ bool pfsWriteFileContextHeaderToMemoryBuffer(PartitionFileSystemFileContext *ctx
LOG_MSG("Invalid parameters!");
return false;
}
PartitionFileSystemHeader *header = &(ctx->header);
u8 *buf_u8 = (u8*)buf;
u64 header_size = 0, full_header_size = 0, block_offset = 0, block_size = 0;
u32 padding_size = 0;
/* Calculate header size. */
header_size = (sizeof(PartitionFileSystemHeader) + (header->entry_count * sizeof(PartitionFileSystemEntry)) + header->name_table_size);
/* Calculate full header size and padding size. */
full_header_size = (IS_ALIGNED(header_size, PFS_FULL_HEADER_ALIGNMENT) ? ALIGN_UP(header_size + 1, PFS_FULL_HEADER_ALIGNMENT) : ALIGN_UP(header_size, PFS_FULL_HEADER_ALIGNMENT));
padding_size = (u32)(full_header_size - header_size);
/* Check buffer size. */
if (buf_size < full_header_size)
{
LOG_MSG("Not enough space available in input buffer to write full Partition FS header! (got 0x%lX, need 0x%lX).", buf_size, full_header_size);
return false;
}
/* Write full header. */
header->name_table_size += padding_size;
block_size = sizeof(PartitionFileSystemHeader);
memcpy(buf_u8 + block_offset, header, block_size);
block_offset += block_size;
header->name_table_size -= padding_size;
block_size = (header->entry_count * sizeof(PartitionFileSystemEntry));
memcpy(buf_u8 + block_offset, ctx->entries, block_size);
block_offset += block_size;
block_size = header->name_table_size;
memcpy(buf_u8 + block_offset, ctx->name_table, block_size);
block_offset += block_size;
memset(buf_u8 + block_offset, 0, padding_size);
/* Update output header size. */
*out_header_size = full_header_size;
return true;
}

View file

@ -67,43 +67,43 @@ bool programInfoInitializeContext(ProgramInfoContext *out, NcaContext *nca_ctx)
LOG_MSG("Invalid parameters!");
return false;
}
u32 i = 0, pfs_entry_count = 0, magic = 0;
NsoContext *tmp_nso_ctx = NULL;
bool success = false;
/* Free output context beforehand. */
programInfoFreeContext(out);
/* Initialize Partition FS context. */
if (!pfsInitializeContext(&(out->pfs_ctx), &(nca_ctx->fs_ctx[0])))
{
LOG_MSG("Failed to initialize Partition FS context!");
goto end;
}
/* Check if we're indeed dealing with an ExeFS. */
if (!out->pfs_ctx.is_exefs)
{
LOG_MSG("Initialized Partition FS is not an ExeFS!");
goto end;
}
/* Get ExeFS entry count. Edge case, we should never trigger this. */
if (!(pfs_entry_count = pfsGetEntryCount(&(out->pfs_ctx))))
{
LOG_MSG("ExeFS has no file entries!");
goto end;
}
/* Initialize NPDM context. */
if (!npdmInitializeContext(&(out->npdm_ctx), &(out->pfs_ctx)))
{
LOG_MSG("Failed to initialize NPDM context!");
goto end;
}
/* Initialize NSO contexts. */
for(i = 0; i < pfs_entry_count; i++)
{
@ -112,49 +112,49 @@ bool programInfoInitializeContext(ProgramInfoContext *out, NcaContext *nca_ctx)
char *pfs_entry_name = pfsGetEntryName(&(out->pfs_ctx), pfs_entry);
if (!pfs_entry || !pfs_entry_name || !strcmp(pfs_entry_name, "main.npdm") || !pfsReadEntryData(&(out->pfs_ctx), pfs_entry, &magic, sizeof(u32), 0) || \
__builtin_bswap32(magic) != NSO_HEADER_MAGIC) continue;
/* Reallocate NSO context buffer. */
if (!(tmp_nso_ctx = realloc(out->nso_ctx, (out->nso_count + 1) * sizeof(NsoContext))))
{
LOG_MSG("Failed to reallocate NSO context buffer for NSO \"%s\"! (entry #%u).", pfs_entry_name, i);
goto end;
}
out->nso_ctx = tmp_nso_ctx;
tmp_nso_ctx = NULL;
memset(&(out->nso_ctx[out->nso_count]), 0, sizeof(NsoContext));
/* Initialize NSO context. */
if (!nsoInitializeContext(&(out->nso_ctx[out->nso_count]), &(out->pfs_ctx), pfs_entry))
{
LOG_MSG("Failed to initialize context for NSO \"%s\"! (entry #%u).", pfs_entry_name, i);
goto end;
}
/* Update NSO count. */
out->nso_count++;
}
/* Safety check. */
if (!out->nso_count)
{
LOG_MSG("ExeFS has no NSOs!");
goto end;
}
/* Update output context. */
out->nca_ctx = nca_ctx;
/* Update content type context info in NCA context. */
nca_ctx->content_type_ctx = out;
nca_ctx->content_type_ctx_patch = false;
success = true;
end:
if (!success) programInfoFreeContext(out);
return success;
}
@ -165,24 +165,24 @@ bool programInfoGenerateAuthoringToolXml(ProgramInfoContext *program_info_ctx)
LOG_MSG("Invalid parameters!");
return false;
}
char *xml_buf = NULL;
u64 xml_buf_size = 0;
char *sdk_version = NULL, *build_type = NULL;
bool is_64bit = (program_info_ctx->npdm_ctx.meta_header->flags.is_64bit_instruction == 1);
u8 *npdm_acid = (u8*)program_info_ctx->npdm_ctx.acid_header;
u64 npdm_acid_size = program_info_ctx->npdm_ctx.meta_header->acid_size, npdm_acid_b64_size = 0;
char *npdm_acid_b64 = NULL;
bool success = false;
/* Free AuthoringTool-like XML data if needed. */
if (program_info_ctx->authoring_tool_xml) free(program_info_ctx->authoring_tool_xml);
program_info_ctx->authoring_tool_xml = NULL;
program_info_ctx->authoring_tool_xml_size = 0;
/* Retrieve the Base64 conversion length for the whole NPDM ACID section. */
mbedtls_base64_encode(NULL, 0, &npdm_acid_b64_size, npdm_acid, npdm_acid_size);
if (npdm_acid_b64_size <= npdm_acid_size)
@ -190,40 +190,40 @@ bool programInfoGenerateAuthoringToolXml(ProgramInfoContext *program_info_ctx)
LOG_MSG("Invalid Base64 conversion length for the NPDM ACID section! (0x%lX, 0x%lX).", npdm_acid_b64_size, npdm_acid_size);
goto end;
}
/* Allocate memory for the NPDM ACID Base64 string. */
if (!(npdm_acid_b64 = calloc(npdm_acid_b64_size + 1, sizeof(char))))
{
LOG_MSG("Failed to allocate 0x%lX bytes for the NPDM ACID section Base64 string!", npdm_acid_b64_size);
goto end;
}
/* Convert NPDM ACID section to a Base64 string. */
if (mbedtls_base64_encode((u8*)npdm_acid_b64, npdm_acid_b64_size + 1, &npdm_acid_b64_size, npdm_acid, npdm_acid_size) != 0)
{
LOG_MSG("Base64 conversion failed for the NPDM ACID section!");
goto end;
}
/* Get SDK version and build type strings. */
if (!programInfoGetSdkVersionAndBuildTypeFromSdkNso(program_info_ctx, &sdk_version, &build_type)) goto end;
if (!PI_ADD_FMT_STR_T1("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" \
"<ProgramInfo>\n")) goto end;
/* SdkVersion. */
if (!programInfoAddStringFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "SdkVersion", sdk_version)) goto end;
if (!PI_ADD_FMT_STR_T1(" <ToolVersion />\n" /* Impossible to get. */ \
" <NxAddonVersion>%s</NxAddonVersion>\n" \
" <PatchToolVersion />\n" /* Impossible to get. */ \
" <BuildTarget>%u</BuildTarget>\n", \
sdk_version, \
is_64bit ? 64 : 32)) goto end;
/* BuildType. */
if (!programInfoAddStringFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "BuildType", build_type)) goto end;
if (!PI_ADD_FMT_STR_T1(" <EnableDeadStrip />\n" /* Impossible to get. */ \
" <EnableDeadStripSpecified />\n" /* Impossible to get. */ \
" <Desc>%s</Desc>\n" \
@ -235,25 +235,25 @@ bool programInfoGenerateAuthoringToolXml(ProgramInfoContext *program_info_ctx)
npdm_acid_b64, \
program_info_ctx->npdm_ctx.acid_header->flags.production ? g_trueString : g_falseString, \
program_info_ctx->npdm_ctx.acid_header->flags.unqualified_approval ? g_trueString : g_falseString)) goto end;
/* MiddlewareList. */
if (!programInfoAddNsoApiListToAuthoringToolXml(&xml_buf, &xml_buf_size, program_info_ctx, "Middleware", "Module", "SDK MW")) goto end;
/* DebugApiList. */
if (!programInfoAddNsoApiListToAuthoringToolXml(&xml_buf, &xml_buf_size, program_info_ctx, "DebugApi", "Api", "SDK Debug")) goto end;
/* PrivateApiList. */
if (!programInfoAddNsoApiListToAuthoringToolXml(&xml_buf, &xml_buf_size, program_info_ctx, "PrivateApi", "Api", "SDK Private")) goto end;
/* GuidelineApiList. */
if (!programInfoAddNsoApiListToAuthoringToolXml(&xml_buf, &xml_buf_size, program_info_ctx, "GuidelineApi", "Api", "SDK Guideline")) goto end;
/* UnresolvedApiList. */
if (!programInfoAddNsoSymbolsToAuthoringToolXml(&xml_buf, &xml_buf_size, program_info_ctx)) goto end;
/* FsAccessControlData. */
if (!programInfoAddFsAccessControlDataToAuthoringToolXml(&xml_buf, &xml_buf_size, program_info_ctx)) goto end;
if (!(success = PI_ADD_FMT_STR_T1(" <EnableGlobalDestructor />\n" /* Impossible to get. */ \
" <EnableGlobalDestructorSpecified />\n" /* Impossible to get. */ \
" <IncludeNssFile />\n" /* Impossible to get. */ \
@ -261,24 +261,24 @@ bool programInfoGenerateAuthoringToolXml(ProgramInfoContext *program_info_ctx)
" <History />\n" /* Impossible to get. */ \
" <TargetTriplet />\n" /* Impossible to get. */ \
"</ProgramInfo>"))) goto end;
/* Update ProgramInfo context. */
program_info_ctx->authoring_tool_xml = xml_buf;
program_info_ctx->authoring_tool_xml_size = strlen(xml_buf);
end:
if (npdm_acid_b64) free(npdm_acid_b64);
if (build_type) free(build_type);
if (sdk_version) free(sdk_version);
if (!success)
{
if (xml_buf) free(xml_buf);
LOG_MSG("Failed to generate ProgramInfo AuthoringTool XML!");
}
return success;
}
@ -289,15 +289,15 @@ static bool programInfoGetSdkVersionAndBuildTypeFromSdkNso(ProgramInfoContext *p
LOG_MSG("Invalid parameters!");
return false;
}
NsoContext *nso_ctx = NULL;
char *sdk_entry = NULL, *sdk_entry_vender = NULL, *sdk_entry_name = NULL, *sdk_entry_version = NULL, *sdk_entry_build_type = NULL;
size_t sdk_entry_version_len = 0;
bool success = true;
/* Set output pointers to NULL beforehand just in case we have no usable nnSdk information. */
*sdk_version = *build_type = NULL;
/* Locate "sdk" NSO. */
for(u32 i = 0; i < program_info_ctx->nso_count; i++)
{
@ -305,55 +305,55 @@ static bool programInfoGetSdkVersionAndBuildTypeFromSdkNso(ProgramInfoContext *p
if (nso_ctx->nso_filename && !strcmp(nso_ctx->nso_filename, "sdk") && nso_ctx->rodata_api_info_section && nso_ctx->rodata_api_info_section_size) break;
nso_ctx = NULL;
}
/* Check if we found the "sdk" NSO. */
if (!nso_ctx) goto end;
/* Look for the "nnSdk" entry in .api_info section. */
for(u64 i = 0; i < nso_ctx->rodata_api_info_section_size; i++)
{
sdk_entry = (nso_ctx->rodata_api_info_section + i);
if (programInfoIsApiInfoEntryValid("SDK ", 4, sdk_entry, &sdk_entry_vender, NULL, &sdk_entry_name, true)) break;
i += strlen(sdk_entry);
sdk_entry = sdk_entry_vender = sdk_entry_name = NULL;
}
/* Bail out if we couldn't find the "nnSdk" entry. */
if (!sdk_entry) goto end;
/* Get the SDK version and build type. */
sdk_entry_version = strchr(sdk_entry_name, '-');
if (!sdk_entry_version) goto end;
sdk_entry_version++;
sdk_entry_build_type = strchr(sdk_entry_version, '-');
if (!sdk_entry_build_type) goto end;
sdk_entry_build_type++;
sdk_entry_version_len = (sdk_entry_build_type - sdk_entry_version - 1);
/* Duplicate strings. */
if (!(*sdk_version = strndup(sdk_entry_version, sdk_entry_version_len)) || !(*build_type = strdup(sdk_entry_build_type)))
{
LOG_MSG("Failed to allocate memory for output strings!");
if (*sdk_version)
{
free(*sdk_version);
*sdk_version = NULL;
}
if (*build_type)
{
free(*build_type);
*build_type = NULL;
}
success = false;
}
end:
return success;
}
@ -365,55 +365,55 @@ static bool programInfoAddNsoApiListToAuthoringToolXml(char **xml_buf, u64 *xml_
int sdk_entry_vender_len = 0;
char *sdk_entry = NULL, *sdk_entry_vender = NULL, *sdk_entry_name = NULL;
bool success = false, api_list_exists = false;
if (!xml_buf || !xml_buf_size || !program_info_ctx || !program_info_ctx->nso_count || !program_info_ctx->nso_ctx || !api_list_tag || !*api_list_tag || !api_entry_prefix || \
!*api_entry_prefix || !sdk_prefix || !(sdk_prefix_len = strlen(sdk_prefix)))
{
LOG_MSG("Invalid parameters!");
return false;
}
/* Check if any entries for this API list exist. */
for(u32 i = 0; i < program_info_ctx->nso_count; i++)
{
NsoContext *nso_ctx = &(program_info_ctx->nso_ctx[i]);
if (!nso_ctx->nso_filename || !*(nso_ctx->nso_filename) || !nso_ctx->rodata_api_info_section || !nso_ctx->rodata_api_info_section_size) continue;
for(u64 j = 0; j < nso_ctx->rodata_api_info_section_size; j++)
{
sdk_entry = (nso_ctx->rodata_api_info_section + j);
if (programInfoIsApiInfoEntryValid(sdk_prefix, sdk_prefix_len, sdk_entry, &sdk_entry_vender, NULL, &sdk_entry_name, false))
{
api_list_exists = true;
break;
}
j += strlen(sdk_entry);
}
if (api_list_exists) break;
}
/* Append an empty XML element if no entries for this API list exist. */
if (!api_list_exists)
{
success = PI_ADD_FMT_STR_T2(" <%sList />\n", api_list_tag);
goto end;
}
if (!PI_ADD_FMT_STR_T2(" <%sList>\n", api_list_tag)) goto end;
/* Retrieve full API list. */
for(u32 i = 0; i < program_info_ctx->nso_count; i++)
{
NsoContext *nso_ctx = &(program_info_ctx->nso_ctx[i]);
if (!nso_ctx->nso_filename || !*(nso_ctx->nso_filename) || !nso_ctx->rodata_api_info_section || !nso_ctx->rodata_api_info_section_size) continue;
for(u64 j = 0; j < nso_ctx->rodata_api_info_section_size; j++)
{
sdk_entry = (nso_ctx->rodata_api_info_section + j);
if (programInfoIsApiInfoEntryValid(sdk_prefix, sdk_prefix_len, sdk_entry, &sdk_entry_vender, &sdk_entry_vender_len, &sdk_entry_name, false))
{
if (!PI_ADD_FMT_STR_T2(" <%s>\n" \
@ -427,13 +427,13 @@ static bool programInfoAddNsoApiListToAuthoringToolXml(char **xml_buf, u64 *xml_
nso_ctx->nso_filename, \
api_list_tag)) goto end;
}
j += strlen(sdk_entry);
}
}
success = PI_ADD_FMT_STR_T2(" </%sList>\n", api_list_tag);
end:
return success;
}
@ -441,20 +441,20 @@ end:
static bool programInfoIsApiInfoEntryValid(const char *sdk_prefix, size_t sdk_prefix_len, char *sdk_entry, char **sdk_entry_vender, int *sdk_entry_vender_len, char **sdk_entry_name, bool nnsdk)
{
if (!sdk_prefix || !sdk_prefix_len || !sdk_entry || !sdk_entry_vender || !sdk_entry_name || strncmp(sdk_entry, sdk_prefix, sdk_prefix_len) != 0) return false;
*sdk_entry_vender = strchr(sdk_entry, '+');
if (!*sdk_entry_vender) return false;
(*sdk_entry_vender)++;
*sdk_entry_name = strchr(*sdk_entry_vender, '+');
if (!*sdk_entry_name) return false;
(*sdk_entry_name)++;
if (sdk_entry_vender_len) *sdk_entry_vender_len = (*sdk_entry_name - *sdk_entry_vender - 1);
int res = strncmp(*sdk_entry_name, g_nnSdkString, g_nnSdkStringLength);
if ((nnsdk && res != 0) || (!nnsdk && res == 0)) return false;
return true;
}
@ -465,7 +465,7 @@ static bool programInfoAddStringFieldToAuthoringToolXml(char **xml_buf, u64 *xml
LOG_MSG("Invalid parameters!");
return false;
}
return ((value && *value) ? PI_ADD_FMT_STR_T2(" <%s>%s</%s>\n", tag_name, value, tag_name) : PI_ADD_FMT_STR_T2(" <%s />\n", tag_name));
}
@ -476,13 +476,13 @@ static bool programInfoAddNsoSymbolsToAuthoringToolXml(char **xml_buf, u64 *xml_
LOG_MSG("Invalid parameters!");
return false;
}
NsoContext *nso_ctx = NULL;
bool success = false, symbols_exist = false, is_64bit = (program_info_ctx->npdm_ctx.meta_header->flags.is_64bit_instruction == 1);
char *symbol_str = NULL;
u64 symbol_size = (!is_64bit ? sizeof(Elf32Symbol) : sizeof(Elf64Symbol));
/* Locate "main" NSO. */
for(u32 i = 0; i < program_info_ctx->nso_count; i++)
{
@ -491,34 +491,34 @@ static bool programInfoAddNsoSymbolsToAuthoringToolXml(char **xml_buf, u64 *xml_
nso_ctx->rodata_dynsym_section && nso_ctx->rodata_dynsym_section_size) break;
nso_ctx = NULL;
}
/* Check if we found the "main" NSO. */
if (!nso_ctx) goto end;
/* Check if any symbols matching the required filters exist. */
for(u64 i = 0; i < nso_ctx->rodata_dynsym_section_size; i += symbol_size)
{
if ((nso_ctx->rodata_dynsym_section_size - i) < symbol_size) break;
if (programInfoIsElfSymbolValid(nso_ctx->rodata_dynsym_section + i, nso_ctx->rodata_dynstr_section, nso_ctx->rodata_dynstr_section_size, is_64bit, NULL))
{
symbols_exist = true;
break;
}
}
/* Bail out if we couldn't find any valid symbols. */
if (!symbols_exist) goto end;
if (!PI_ADD_FMT_STR_T2(" <UnresolvedApiList>\n")) goto end;
/* Parse ELF dynamic symbol table to retrieve the symbol strings. */
for(u64 i = 0; i < nso_ctx->rodata_dynsym_section_size; i += symbol_size)
{
if ((nso_ctx->rodata_dynsym_section_size - i) < symbol_size) break;
if (!programInfoIsElfSymbolValid(nso_ctx->rodata_dynsym_section + i, nso_ctx->rodata_dynstr_section, nso_ctx->rodata_dynstr_section_size, is_64bit, &symbol_str)) continue;
if (!PI_ADD_FMT_STR_T2(" <UnresolvedApi>\n" \
" <ApiName>%s</ApiName>\n" \
" <NsoName>%s</NsoName>\n" \
@ -526,23 +526,23 @@ static bool programInfoAddNsoSymbolsToAuthoringToolXml(char **xml_buf, u64 *xml_
symbol_str, \
nso_ctx->nso_filename)) goto end;
}
success = PI_ADD_FMT_STR_T2(" </UnresolvedApiList>\n");
end:
/* Append an empty XML element if no valid symbols exist. */
if (!success && (!nso_ctx || !symbols_exist)) success = PI_ADD_FMT_STR_T2(" <UnresolvedApiList />\n");
return success;
}
static bool programInfoIsElfSymbolValid(u8 *dynsym_ptr, char *dynstr_base_ptr, u64 dynstr_size, bool is_64bit, char **symbol_str)
{
if (!dynsym_ptr || !dynstr_base_ptr || !dynstr_size) return false;
u8 st_type = 0;
bool is_valid = false;
if (!is_64bit)
{
/* Parse 32-bit ELF symbol. */
@ -557,7 +557,7 @@ static bool programInfoIsElfSymbolValid(u8 *dynsym_ptr, char *dynstr_base_ptr, u
is_valid = (elf64_symbol->st_name < dynstr_size && (st_type == STT_NOTYPE || st_type == STT_FUNC) && elf64_symbol->st_shndx == SHN_UNDEF);
if (is_valid && symbol_str) *symbol_str = (dynstr_base_ptr + elf64_symbol->st_name);
}
return is_valid;
}
@ -567,17 +567,17 @@ static bool programInfoAddFsAccessControlDataToAuthoringToolXml(char **xml_buf,
NpdmFsAccessControlDataSaveDataOwnerBlock *save_data_owner_block = NULL;
u64 *save_data_owner_ids = NULL;
bool success = false, sdo_data_available = false;
if (!xml_buf || !xml_buf_size || !program_info_ctx || !(aci_fac_data = program_info_ctx->npdm_ctx.aci_fac_data))
{
LOG_MSG("Invalid parameters!");
return false;
}
/* Check if there's save data owner data available in the FS access control data region from the ACI0 section in the NPDM. */
sdo_data_available = (aci_fac_data->save_data_owner_info_offset >= sizeof(NpdmFsAccessControlData) && aci_fac_data->save_data_owner_info_size);
if (!sdo_data_available) goto end;
/* Get save data owner block and check the ID count. */
save_data_owner_block = (NpdmFsAccessControlDataSaveDataOwnerBlock*)((u8*)aci_fac_data + aci_fac_data->save_data_owner_info_offset);
if (!save_data_owner_block->save_data_owner_id_count)
@ -585,13 +585,13 @@ static bool programInfoAddFsAccessControlDataToAuthoringToolXml(char **xml_buf,
sdo_data_available = false;
goto end;
}
/* Get save data owner IDs. */
/* Padding to a 0x4-byte boundary is needed. Each accessibility field takes up a single byte, so we can get away with it by aligning the ID count. */
save_data_owner_ids = (u64*)((u8*)save_data_owner_block + sizeof(NpdmFsAccessControlDataSaveDataOwnerBlock) + ALIGN_UP(save_data_owner_block->save_data_owner_id_count, 0x4));
if (!PI_ADD_FMT_STR_T2(" <FsAccessControlData>\n")) goto end;
/* Append save data owner IDs. */
for(u32 i = 0; i < save_data_owner_block->save_data_owner_id_count; i++)
{
@ -602,12 +602,12 @@ static bool programInfoAddFsAccessControlDataToAuthoringToolXml(char **xml_buf,
g_facAccessibilityStrings[save_data_owner_block->accessibility[i] & 0x3], \
save_data_owner_ids[i])) goto end;
}
success = PI_ADD_FMT_STR_T2(" </FsAccessControlData>\n");
end:
/* Append an empty XML element if no FS access control data exists. */
if (!success && !sdo_data_available) success = PI_ADD_FMT_STR_T2(" <FsAccessControlData />\n");
return success;
}

View file

@ -32,7 +32,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base
u64 dir_table_offset = 0, file_table_offset = 0;
NcaContext *base_nca_ctx = NULL, *patch_nca_ctx = NULL;
bool dump_fs_header = false, success = false;
if (!out || !base_nca_fs_ctx || !base_nca_fs_ctx->enabled || (base_nca_fs_ctx->has_sparse_layer && !patch_nca_fs_ctx) || !(base_nca_ctx = (NcaContext*)base_nca_fs_ctx->nca_ctx) || \
(base_nca_ctx->format_version == NcaVersion_Nca0 && (base_nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || \
base_nca_fs_ctx->hash_type != NcaHashType_HierarchicalSha256)) || (base_nca_ctx->format_version != NcaVersion_Nca0 && \
@ -44,20 +44,20 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base
LOG_MSG("Invalid parameters!");
return false;
}
/* Free output context beforehand. */
romfsFreeContext(out);
NcaStorageContext *base_storage_ctx = &(out->storage_ctx[0]), *patch_storage_ctx = &(out->storage_ctx[1]);
bool is_nca0_romfs = (base_nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs);
/* Initialize base NCA storage context. */
if (!ncaStorageInitializeContext(base_storage_ctx, base_nca_fs_ctx))
{
LOG_MSG("Failed to initialize base NCA storage context!");
goto end;
}
if (patch_nca_fs_ctx)
{
/* Initialize base NCA storage context. */
@ -66,14 +66,14 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base
LOG_MSG("Failed to initialize patch NCA storage context!");
goto end;
}
/* Set patch NCA storage original substorage. */
if (!ncaStorageSetPatchOriginalSubStorage(patch_storage_ctx, base_storage_ctx))
{
LOG_MSG("Failed to set patch NCA storage context's original substorage!");
goto end;
}
/* Set default NCA FS storage context. */
out->is_patch = true;
out->default_storage_ctx = patch_storage_ctx;
@ -82,76 +82,76 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base
out->is_patch = false;
out->default_storage_ctx = base_storage_ctx;
}
/* Get RomFS offset and size. */
if (!ncaStorageGetHashTargetExtents(out->default_storage_ctx, &(out->offset), &(out->size)))
{
LOG_MSG("Failed to get target hash layer extents!");
goto end;
}
/* Read RomFS header. */
if (!ncaStorageRead(out->default_storage_ctx, &(out->header), sizeof(RomFileSystemHeader), out->offset))
{
LOG_MSG("Failed to read RomFS header!");
goto end;
}
if ((is_nca0_romfs && out->header.old_format.header_size != ROMFS_OLD_HEADER_SIZE) || (!is_nca0_romfs && out->header.cur_format.header_size != ROMFS_HEADER_SIZE))
{
LOG_MSG("Invalid RomFS header size!");
dump_fs_header = true;
goto end;
}
/* Read directory entries table. */
dir_table_offset = (is_nca0_romfs ? (u64)out->header.old_format.directory_entry_offset : out->header.cur_format.directory_entry_offset);
out->dir_table_size = (is_nca0_romfs ? (u64)out->header.old_format.directory_entry_size : out->header.cur_format.directory_entry_size);
if (!out->dir_table_size || (dir_table_offset + out->dir_table_size) > out->size)
{
LOG_MSG("Invalid RomFS directory entries table!");
dump_fs_header = true;
goto end;
}
out->dir_table = malloc(out->dir_table_size);
if (!out->dir_table)
{
LOG_MSG("Unable to allocate memory for RomFS directory entries table!");
goto end;
}
if (!ncaStorageRead(out->default_storage_ctx, out->dir_table, out->dir_table_size, out->offset + dir_table_offset))
{
LOG_MSG("Failed to read RomFS directory entries table!");
goto end;
}
/* Read file entries table. */
file_table_offset = (is_nca0_romfs ? (u64)out->header.old_format.file_entry_offset : out->header.cur_format.file_entry_offset);
out->file_table_size = (is_nca0_romfs ? (u64)out->header.old_format.file_entry_size : out->header.cur_format.file_entry_size);
if (!out->file_table_size || (file_table_offset + out->file_table_size) > out->size)
{
LOG_MSG("Invalid RomFS file entries table!");
dump_fs_header = true;
goto end;
}
out->file_table = malloc(out->file_table_size);
if (!out->file_table)
{
LOG_MSG("Unable to allocate memory for RomFS file entries table!");
goto end;
}
if (!ncaStorageRead(out->default_storage_ctx, out->file_table, out->file_table_size, out->offset + file_table_offset))
{
LOG_MSG("Failed to read RomFS file entries table!");
goto end;
}
/* Get file data body offset. */
out->body_offset = (is_nca0_romfs ? (u64)out->header.old_format.body_offset : out->header.cur_format.body_offset);
if (out->body_offset >= out->size)
@ -160,18 +160,18 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base
dump_fs_header = true;
goto end;
}
/* Update flag. */
success = true;
end:
if (!success)
{
if (dump_fs_header) LOG_DATA(&(out->header), sizeof(RomFileSystemHeader), "RomFS header dump:");
romfsFreeContext(out);
}
return success;
}
@ -182,14 +182,14 @@ bool romfsReadFileSystemData(RomFileSystemContext *ctx, void *out, u64 read_size
LOG_MSG("Invalid parameters!");
return false;
}
/* Read filesystem data. */
if (!ncaStorageRead(ctx->default_storage_ctx, out, read_size, ctx->offset + offset))
{
LOG_MSG("Failed to read RomFS data!");
return false;
}
return true;
}
@ -200,14 +200,14 @@ bool romfsReadFileEntryData(RomFileSystemContext *ctx, RomFileSystemFileEntry *f
LOG_MSG("Invalid parameters!");
return false;
}
/* Read entry data. */
if (!romfsReadFileSystemData(ctx, out, read_size, ctx->body_offset + file_entry->offset + offset))
{
LOG_MSG("Failed to read RomFS file entry data!");
return false;
}
return true;
}
@ -218,10 +218,10 @@ bool romfsGetTotalDataSize(RomFileSystemContext *ctx, u64 *out_size)
LOG_MSG("Invalid parameters!");
return false;
}
u64 offset = 0, total_size = 0;
RomFileSystemFileEntry *file_entry = NULL;
while(offset < ctx->file_table_size)
{
if (!(file_entry = romfsGetFileEntryByOffset(ctx, offset)))
@ -229,13 +229,13 @@ bool romfsGetTotalDataSize(RomFileSystemContext *ctx, u64 *out_size)
LOG_MSG("Failed to retrieve file entry!");
return false;
}
total_size += file_entry->size;
offset += ALIGN_UP(sizeof(RomFileSystemFileEntry) + file_entry->name_length, 4);
}
*out_size = total_size;
return true;
}
@ -247,12 +247,12 @@ bool romfsGetDirectoryDataSize(RomFileSystemContext *ctx, RomFileSystemDirectory
LOG_MSG("Invalid parameters!");
return false;
}
u64 total_size = 0, child_dir_size = 0;
u32 cur_file_offset = 0, cur_dir_offset = 0;
RomFileSystemFileEntry *cur_file_entry = NULL;
RomFileSystemDirectoryEntry *cur_dir_entry = NULL;
cur_file_offset = dir_entry->file_offset;
while(cur_file_offset != ROMFS_VOID_ENTRY)
{
@ -261,11 +261,11 @@ bool romfsGetDirectoryDataSize(RomFileSystemContext *ctx, RomFileSystemDirectory
LOG_MSG("Failed to retrieve file entry!");
return false;
}
total_size += cur_file_entry->size;
cur_file_offset = cur_file_entry->next_offset;
}
cur_dir_offset = dir_entry->directory_offset;
while(cur_dir_offset != ROMFS_VOID_ENTRY)
{
@ -274,13 +274,13 @@ bool romfsGetDirectoryDataSize(RomFileSystemContext *ctx, RomFileSystemDirectory
LOG_MSG("Failed to retrieve directory entry/size!");
return false;
}
total_size += child_dir_size;
cur_dir_offset = cur_dir_entry->next_offset;
}
*out_size = total_size;
return true;
}
@ -289,23 +289,23 @@ RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByPath(RomFileSystemContext *
size_t path_len = 0;
char *path_dup = NULL, *pch = NULL, *state = NULL;
RomFileSystemDirectoryEntry *dir_entry = NULL;
if (!ctx || !ctx->dir_table || !ctx->dir_table_size || !path || *path != '/' || !(path_len = strlen(path)) || !(dir_entry = romfsGetDirectoryEntryByOffset(ctx, 0)))
{
LOG_MSG("Invalid parameters!");
return NULL;
}
/* Check if the root directory was requested. */
if (path_len == 1) return dir_entry;
/* Duplicate path to avoid problems with strtok_r(). */
if (!(path_dup = strdup(path)))
{
LOG_MSG("Unable to duplicate input path! (\"%s\").", path);
return NULL;
}
pch = strtok_r(path_dup, "/", &state);
if (!pch)
{
@ -313,7 +313,7 @@ RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByPath(RomFileSystemContext *
dir_entry = NULL;
goto end;
}
while(pch)
{
if (!(dir_entry = romfsGetChildDirectoryEntryByName(ctx, dir_entry, pch)))
@ -321,13 +321,13 @@ RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByPath(RomFileSystemContext *
LOG_MSG("Failed to retrieve directory entry by name for \"%s\"! (\"%s\").", pch, path);
break;
}
pch = strtok_r(NULL, "/", &state);
}
end:
if (path_dup) free(path_dup);
return dir_entry;
}
@ -339,40 +339,40 @@ RomFileSystemFileEntry *romfsGetFileEntryByPath(RomFileSystemContext *ctx, const
RomFileSystemFileEntry *file_entry = NULL;
RomFileSystemDirectoryEntry *dir_entry = NULL;
NcaContext *nca_ctx = NULL;
if (!ctx || !ctx->file_table || !ctx->file_table_size || !ncaStorageIsValidContext(ctx->default_storage_ctx) || \
!(nca_ctx = (NcaContext*)ctx->default_storage_ctx->nca_fs_ctx->nca_ctx) || !path || *path != '/' || (path_len = strlen(path)) <= 1)
{
LOG_MSG("Invalid parameters!");
return NULL;
}
content_type = nca_ctx->content_type;
/* Duplicate path. */
if (!(path_dup = strdup(path)))
{
LOG_MSG("Unable to duplicate input path! (\"%s\").", path);
return NULL;
}
/* Remove any trailing slashes. */
while(path_dup[path_len - 1] == '/')
{
path_dup[path_len - 1] = '\0';
path_len--;
}
/* Safety check. */
if (!path_len || !(filename = strrchr(path_dup, '/')))
{
LOG_MSG("Invalid input path! (\"%s\").", path);
goto end;
}
/* Remove leading slash and adjust filename string pointer. */
*filename++ = '\0';
/* Retrieve directory entry. */
/* If the first character is NULL, then just retrieve the root directory entry. */
if (!(dir_entry = (*path_dup ? romfsGetDirectoryEntryByPath(ctx, path_dup) : romfsGetDirectoryEntryByOffset(ctx, 0))))
@ -380,7 +380,7 @@ RomFileSystemFileEntry *romfsGetFileEntryByPath(RomFileSystemContext *ctx, const
LOG_MSG("Failed to retrieve directory entry for \"%s\"! (\"%s\").", *path_dup ? path_dup : "/", path);
goto end;
}
/* Retrieve file entry. */
if (!(file_entry = romfsGetChildFileEntryByName(ctx, dir_entry, filename)))
{
@ -388,10 +388,10 @@ RomFileSystemFileEntry *romfsGetFileEntryByPath(RomFileSystemContext *ctx, const
bool skip_log = ((!strncmp(path, "/icon_", 6) && content_type == NcmContentType_Control) || (!strcmp(path, "/legalinfo.xml") && content_type == NcmContentType_LegalInformation));
if (!skip_log) LOG_MSG("Failed to retrieve file entry by name for \"%s\"! (\"%s\").", filename, path);
}
end:
if (path_dup) free(path_dup);
return file_entry;
}
@ -401,21 +401,21 @@ bool romfsGeneratePathFromDirectoryEntry(RomFileSystemContext *ctx, RomFileSyste
u32 dir_offset = ROMFS_VOID_ENTRY, dir_entries_count = 0;
RomFileSystemDirectoryEntry **dir_entries = NULL, **tmp_dir_entries = NULL;
bool success = false;
if (!ctx || !ctx->dir_table || !ctx->dir_table_size || !dir_entry || (!dir_entry->name_length && dir_entry->parent_offset) || !out_path || out_path_size < 2 || \
illegal_char_replace_type > RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly)
{
LOG_MSG("Invalid parameters!");
return false;
}
/* Check if we're dealing with the root directory entry. */
if (!dir_entry->name_length)
{
sprintf(out_path, "/");
return true;
}
/* Allocate memory for our directory entries. */
dir_entries = calloc(1, sizeof(RomFileSystemDirectoryEntry*));
if (!dir_entries)
@ -423,55 +423,55 @@ bool romfsGeneratePathFromDirectoryEntry(RomFileSystemContext *ctx, RomFileSyste
LOG_MSG("Unable to allocate memory for directory entries!");
return false;
}
path_len = (1 + dir_entry->name_length);
*dir_entries = dir_entry;
dir_entries_count++;
while(true)
{
dir_offset = dir_entries[dir_entries_count - 1]->parent_offset;
if (!dir_offset) break;
/* Reallocate directory entries. */
if (!(tmp_dir_entries = realloc(dir_entries, (dir_entries_count + 1) * sizeof(RomFileSystemDirectoryEntry*))))
{
LOG_MSG("Unable to reallocate directory entries buffer!");
goto end;
}
dir_entries = tmp_dir_entries;
tmp_dir_entries = NULL;
RomFileSystemDirectoryEntry **cur_dir_entry = &(dir_entries[dir_entries_count]);
if (!(*cur_dir_entry = romfsGetDirectoryEntryByOffset(ctx, dir_offset)) || !(*cur_dir_entry)->name_length)
{
LOG_MSG("Failed to retrieve directory entry!");
goto end;
}
path_len += (1 + (*cur_dir_entry)->name_length);
dir_entries_count++;
}
if (path_len >= out_path_size)
{
LOG_MSG("Output path length exceeds output buffer size!");
goto end;
}
/* Generate output path. */
*out_path = '\0';
path_len = 0;
for(u32 i = dir_entries_count; i > 0; i--)
{
RomFileSystemDirectoryEntry **cur_dir_entry = &(dir_entries[i - 1]);
strcat(out_path, "/");
strncat(out_path, (*cur_dir_entry)->name, (*cur_dir_entry)->name_length);
path_len++;
if (illegal_char_replace_type)
{
utilsReplaceIllegalCharacters(out_path + path_len, illegal_char_replace_type == RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly);
@ -480,12 +480,12 @@ bool romfsGeneratePathFromDirectoryEntry(RomFileSystemContext *ctx, RomFileSyste
path_len += (*cur_dir_entry)->name_length;
}
}
success = true;
end:
if (dir_entries) free(dir_entries);
return success;
}
@ -493,21 +493,21 @@ bool romfsGeneratePathFromFileEntry(RomFileSystemContext *ctx, RomFileSystemFile
{
size_t path_len = 0;
RomFileSystemDirectoryEntry *dir_entry = NULL;
if (!ctx || !ctx->file_table || !ctx->file_table_size || !file_entry || !file_entry->name_length || !out_path || out_path_size < 2 || \
!(dir_entry = romfsGetDirectoryEntryByOffset(ctx, file_entry->parent_offset)) || illegal_char_replace_type > RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly)
{
LOG_MSG("Invalid parameters!");
return false;
}
/* Retrieve full RomFS path up to the file entry name. */
if (!romfsGeneratePathFromDirectoryEntry(ctx, dir_entry, out_path, out_path_size, illegal_char_replace_type))
{
LOG_MSG("Failed to retrieve RomFS directory path!");
return false;
}
/* Check path length. */
path_len = strlen(out_path);
if ((1 + file_entry->name_length) >= (out_path_size - path_len))
@ -515,18 +515,18 @@ bool romfsGeneratePathFromFileEntry(RomFileSystemContext *ctx, RomFileSystemFile
LOG_MSG("Output path length exceeds output buffer size!");
return false;
}
/* Concatenate file entry name. */
if (file_entry->parent_offset)
{
strcat(out_path, "/");
path_len++;
}
strncat(out_path, file_entry->name, file_entry->name_length);
if (illegal_char_replace_type) utilsReplaceIllegalCharacters(out_path + path_len, illegal_char_replace_type == RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly);
return true;
}
@ -539,11 +539,11 @@ bool romfsGenerateFileEntryPatch(RomFileSystemContext *ctx, RomFileSystemFileEnt
LOG_MSG("Invalid parameters!");
return false;
}
NcaFsSectionContext *nca_fs_ctx = ctx->default_storage_ctx->nca_fs_ctx;
u64 fs_offset = (ctx->body_offset + file_entry->offset + data_offset);
bool success = false;
if (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs)
{
out->use_old_format_patch = true;
@ -552,12 +552,12 @@ bool romfsGenerateFileEntryPatch(RomFileSystemContext *ctx, RomFileSystemFileEnt
out->use_old_format_patch = false;
success = ncaGenerateHierarchicalIntegrityPatch(nca_fs_ctx, data, data_size, fs_offset, &(out->cur_format_patch));
}
out->written = false;
if (!success) LOG_MSG("Failed to generate 0x%lX bytes Hierarchical%s patch at offset 0x%lX for RomFS file entry!", data_size, \
nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? "Sha256" : "Integrity", fs_offset);
return success;
}
@ -566,13 +566,13 @@ static RomFileSystemDirectoryEntry *romfsGetChildDirectoryEntryByName(RomFileSys
u64 dir_offset = 0;
size_t name_len = 0;
RomFileSystemDirectoryEntry *child_dir_entry = NULL;
if (!ctx || !ctx->dir_table || !ctx->dir_table_size || !dir_entry || (dir_offset = dir_entry->directory_offset) == ROMFS_VOID_ENTRY || !name || !(name_len = strlen(name)))
{
LOG_MSG("Invalid parameters!");
return NULL;
}
while(dir_offset != ROMFS_VOID_ENTRY)
{
if (!(child_dir_entry = romfsGetDirectoryEntryByOffset(ctx, dir_offset)))
@ -580,14 +580,14 @@ static RomFileSystemDirectoryEntry *romfsGetChildDirectoryEntryByName(RomFileSys
LOG_MSG("Failed to retrieve directory entry at offset 0x%lX!", dir_offset);
break;
}
/* strncmp() is used here instead of strcmp() because names stored in RomFS sections are not always NULL terminated. */
/* If the name ends at a 4-byte boundary, the next entry starts immediately. */
if (child_dir_entry->name_length == name_len && !strncmp(child_dir_entry->name, name, name_len)) return child_dir_entry;
dir_offset = child_dir_entry->next_offset;
}
return NULL;
}
@ -596,14 +596,14 @@ static RomFileSystemFileEntry *romfsGetChildFileEntryByName(RomFileSystemContext
u64 file_offset = 0;
size_t name_len = 0;
RomFileSystemFileEntry *child_file_entry = NULL;
if (!ctx || !ctx->dir_table || !ctx->dir_table_size || !ctx->file_table || !ctx->file_table_size || !dir_entry || (file_offset = dir_entry->file_offset) == ROMFS_VOID_ENTRY || \
!name || !(name_len = strlen(name)))
{
LOG_MSG("Invalid parameters!");
return NULL;
}
while(file_offset != ROMFS_VOID_ENTRY)
{
if (!(child_file_entry = romfsGetFileEntryByOffset(ctx, file_offset)))
@ -611,13 +611,13 @@ static RomFileSystemFileEntry *romfsGetChildFileEntryByName(RomFileSystemContext
LOG_MSG("Failed to retrieve file entry at offset 0x%lX!", file_offset);
break;
}
/* strncmp() is used here instead of strcmp() because names stored in RomFS sections are not always NULL terminated. */
/* If the name ends at a 4-byte boundary, the next entry starts immediately. */
if (child_file_entry->name_length == name_len && !strncmp(child_file_entry->name, name, name_len)) return child_file_entry;
file_offset = child_file_entry->next_offset;
}
return NULL;
}

View file

@ -35,15 +35,15 @@ bool rsa2048VerifySha256BasedPssSignature(const void *data, size_t data_size, co
LOG_MSG("Invalid parameters!");
return false;
}
int mbedtls_ret = 0;
mbedtls_rsa_context rsa;
u8 hash[SHA256_HASH_SIZE] = {0};
bool ret = false;
/* Initialize RSA context. */
mbedtls_rsa_init(&rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
/* Import RSA parameters. */
mbedtls_ret = mbedtls_rsa_import_raw(&rsa, (const u8*)modulus, RSA2048_BYTES, NULL, 0, NULL, 0, NULL, 0, (const u8*)public_exponent, public_exponent_size);
if (mbedtls_ret != 0)
@ -51,10 +51,10 @@ bool rsa2048VerifySha256BasedPssSignature(const void *data, size_t data_size, co
LOG_MSG("mbedtls_rsa_import_raw failed! (%d).", mbedtls_ret);
goto end;
}
/* Calculate SHA-256 checksum for the input data. */
sha256CalculateHash(hash, data, data_size);
/* Verify signature. */
mbedtls_ret = mbedtls_rsa_rsassa_pss_verify(&rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, MBEDTLS_MD_SHA256, SHA256_HASH_SIZE, hash, (const u8*)signature);
if (mbedtls_ret != 0)
@ -62,12 +62,12 @@ bool rsa2048VerifySha256BasedPssSignature(const void *data, size_t data_size, co
LOG_MSG("mbedtls_rsa_rsassa_pss_verify failed! (%d).", mbedtls_ret);
goto end;
}
ret = true;
end:
mbedtls_rsa_free(&rsa);
return ret;
}
@ -80,20 +80,20 @@ bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const
LOG_MSG("Invalid parameters!");
return false;
}
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_rsa_context rsa;
const char *pers = __func__;
int mbedtls_ret = 0;
bool ret = false;
/* Initialize contexts. */
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_rsa_init(&rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
/* Seed the random number generator. */
mbedtls_ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const u8*)pers, strlen(pers));
if (mbedtls_ret != 0)
@ -101,7 +101,7 @@ bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const
LOG_MSG("mbedtls_ctr_drbg_seed failed! (%d).", mbedtls_ret);
goto end;
}
/* Import RSA parameters. */
mbedtls_ret = mbedtls_rsa_import_raw(&rsa, (const u8*)modulus, RSA2048_BYTES, NULL, 0, NULL, 0, (const u8*)private_exponent, private_exponent_size, (const u8*)public_exponent, public_exponent_size);
if (mbedtls_ret != 0)
@ -109,7 +109,7 @@ bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const
LOG_MSG("mbedtls_rsa_import_raw failed! (%d).", mbedtls_ret);
goto end;
}
/* Derive RSA prime factors. */
mbedtls_ret = mbedtls_rsa_complete(&rsa);
if (mbedtls_ret != 0)
@ -117,7 +117,7 @@ bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const
LOG_MSG("mbedtls_rsa_complete failed! (%d).", mbedtls_ret);
goto end;
}
/* Perform RSA-OAEP decryption. */
mbedtls_ret = mbedtls_rsa_rsaes_oaep_decrypt(&rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, (const u8*)label, label_size, out_size, (const u8*)signature, (u8*)dst, dst_size);
if (mbedtls_ret != 0)
@ -125,13 +125,13 @@ bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const
LOG_MSG("mbedtls_rsa_rsaes_oaep_decrypt failed! (%d).", mbedtls_ret);
goto end;
}
ret = true;
end:
mbedtls_rsa_free(&rsa);
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
return ret;
}

File diff suppressed because it is too large Load diff

View file

@ -84,16 +84,16 @@ static const u32 g_atmosphereTipcVersion = MAKEHOSVERSION(0, 19, 0);
bool servicesInitialize(void)
{
bool ret = true;
SCOPED_LOCK(&g_servicesMutex)
{
for(u32 i = 0; i < g_serviceInfoCount; i++)
{
ServiceInfo *service_info = &(g_serviceInfo[i]);
/* Check if this service has been already initialized. */
if (service_info->initialized) continue;
/* Check if this service depends on a condition function. */
if (service_info->cond_func != NULL)
{
@ -101,10 +101,10 @@ bool servicesInitialize(void)
/* Skip this service if the required conditions aren't met. */
if (!service_info->cond_func(service_info)) continue;
}
/* Check if this service actually has a valid initialization function. */
if (service_info->init_func == NULL) continue;
/* Initialize service. */
Result rc = service_info->init_func();
if (R_FAILED(rc))
@ -113,12 +113,12 @@ bool servicesInitialize(void)
ret = false;
break;
}
/* Update flag. */
service_info->initialized = true;
}
}
return ret;
}
@ -129,13 +129,13 @@ void servicesClose(void)
for(u32 i = 0; i < g_serviceInfoCount; i++)
{
ServiceInfo *service_info = &(g_serviceInfo[i]);
/* Check if this service has not been initialized, or if it doesn't have a valid close function. */
if (!service_info->initialized || service_info->close_func == NULL) continue;
/* Close service. */
service_info->close_func();
/* Update flag. */
service_info->initialized = false;
}
@ -152,7 +152,7 @@ bool servicesCheckInitializedServiceByName(const char *name)
bool servicesCheckRunningServiceByName(const char *name)
{
bool ret = false;
SCOPED_LOCK(&g_servicesMutex)
{
if (!name || !*name || !_servicesCheckInitializedServiceByName("spl:"))
@ -160,11 +160,11 @@ bool servicesCheckRunningServiceByName(const char *name)
LOG_MSG("Invalid parameters!");
break;
}
Result rc = servicesAtmosphereHasService(&ret, smEncodeName(name));
if (R_FAILED(rc)) LOG_MSG("servicesAtmosphereHasService failed for \"%s\"! (0x%08X).", name, rc);
}
return ret;
}
@ -177,9 +177,9 @@ void servicesChangeHardwareClockRates(u32 cpu_rate, u32 mem_rate)
LOG_MSG("Error: clock service uninitialized.");
break;
}
Result rc1 = 0, rc2 = 0;
if (g_clkSvcUsePcv)
{
rc1 = pcvSetClockRate(PcvModule_CpuBus, cpu_rate);
@ -188,7 +188,7 @@ void servicesChangeHardwareClockRates(u32 cpu_rate, u32 mem_rate)
rc1 = clkrstSetClockRate(&g_clkrstCpuSession, cpu_rate);
rc2 = clkrstSetClockRate(&g_clkrstMemSession, mem_rate);
}
if (R_FAILED(rc1)) LOG_MSG("%sSetClockRate failed! (0x%08X) (CPU).", (g_clkSvcUsePcv ? "pcv" : "clkrst"), rc1);
if (R_FAILED(rc2)) LOG_MSG("%sSetClockRate failed! (0x%08X) (MEM).", (g_clkSvcUsePcv ? "pcv" : "clkrst"), rc2);
}
@ -197,20 +197,20 @@ void servicesChangeHardwareClockRates(u32 cpu_rate, u32 mem_rate)
static bool _servicesCheckInitializedServiceByName(const char *name)
{
if (!name || !*name) return false;
bool ret = false;
for(u32 i = 0; i < g_serviceInfoCount; i++)
{
ServiceInfo *service_info = &(g_serviceInfo[i]);
if (!strcmp(service_info->name, name))
{
ret = service_info->initialized;
break;
}
}
return ret;
}
@ -218,17 +218,17 @@ static bool _servicesCheckInitializedServiceByName(const char *name)
static Result servicesAtmosphereHasService(bool *out, SmServiceName name)
{
if (!out || !name.name[0]) return MAKERESULT(Module_Libnx, LibnxError_BadInput);
u8 tmp = 0;
Result rc = 0;
/* Get Exosphère API version. */
if (!g_atmosphereVersion)
{
rc = servicesGetExosphereApiVersion(&g_atmosphereVersion);
if (R_FAILED(rc)) LOG_MSG("servicesGetExosphereApiVersion failed! (0x%08X).", rc);
}
/* Check if service is running. */
/* Dispatch IPC request using CMIF or TIPC serialization depending on our current environment. */
if (hosversionAtLeast(12, 0, 0) || g_atmosphereVersion >= g_atmosphereTipcVersion)
@ -237,9 +237,9 @@ static Result servicesAtmosphereHasService(bool *out, SmServiceName name)
} else {
rc = serviceDispatchInOut(smGetServiceSession(), g_smAtmosphereHasService, name, tmp);
}
if (R_SUCCEEDED(rc)) *out = (tmp != 0);
return rc;
}
@ -247,18 +247,18 @@ static Result servicesAtmosphereHasService(bool *out, SmServiceName name)
static Result servicesGetExosphereApiVersion(u32 *out)
{
if (!out) return MAKERESULT(Module_Libnx, LibnxError_BadInput);
Result rc = 0;
u64 cfg = 0;
u32 version = 0;
rc = splGetConfig(SplConfigItem_ExosphereApiVersion, &cfg);
if (R_SUCCEEDED(rc))
{
*out = version = (u32)((cfg >> 40) & 0xFFFFFF);
LOG_MSG("Exosphère API version: %u.%u.%u.", HOSVER_MAJOR(version), HOSVER_MINOR(version), HOSVER_MICRO(version));
}
return rc;
}
@ -270,7 +270,7 @@ static Result servicesNifmUserInitialize(void)
static Result servicesClkrstInitialize(void)
{
Result rc = 0;
/* Open clkrst service handle. */
rc = clkrstInitialize();
if (R_FAILED(rc))
@ -278,11 +278,11 @@ static Result servicesClkrstInitialize(void)
LOG_MSG("clkrstInitialize failed! (0x%08X).", rc);
return rc;
}
/* Initialize CPU and MEM clkrst sessions. */
memset(&g_clkrstCpuSession, 0, sizeof(ClkrstSession));
memset(&g_clkrstMemSession, 0, sizeof(ClkrstSession));
rc = clkrstOpenSession(&g_clkrstCpuSession, PcvModuleId_CpuBus, 3);
if (R_FAILED(rc))
{
@ -290,7 +290,7 @@ static Result servicesClkrstInitialize(void)
clkrstExit();
return rc;
}
rc = clkrstOpenSession(&g_clkrstMemSession, PcvModuleId_EMC, 3);
if (R_FAILED(rc))
{
@ -298,7 +298,7 @@ static Result servicesClkrstInitialize(void)
clkrstCloseSession(&g_clkrstCpuSession);
clkrstExit();
}
return rc;
}
@ -307,7 +307,7 @@ static void servicesClkrstExit(void)
/* Close CPU and MEM clkrst sessions. */
clkrstCloseSession(&g_clkrstMemSession);
clkrstCloseSession(&g_clkrstCpuSession);
/* Close clkrst service handle. */
clkrstExit();
}
@ -315,30 +315,30 @@ static void servicesClkrstExit(void)
static bool servicesClkGetServiceType(void *arg)
{
if (!arg) return false;
ServiceInfo *info = (ServiceInfo*)arg;
if (strcmp(info->name, "clk") != 0 || info->init_func != NULL || info->close_func != NULL) return false;
/* Determine which service needs to be used to control hardware clock rates, depending on the system version. */
/* This may either be pcv (sysver lower than 8.0.0) or clkrst (sysver equal to or greater than 8.0.0). */
g_clkSvcUsePcv = hosversionBefore(8, 0, 0);
/* Fill service info. */
sprintf(info->name, "%s", (g_clkSvcUsePcv ? "pcv" : "clkrst"));
info->cond_func = NULL;
info->init_func = (g_clkSvcUsePcv ? &pcvInitialize : &servicesClkrstInitialize);
info->close_func = (g_clkSvcUsePcv ? &pcvExit : &servicesClkrstExit);
return true;
}
static bool servicesSplCryptoCheckAvailability(void *arg)
{
if (!arg) return false;
ServiceInfo *info = (ServiceInfo*)arg;
if (strcmp(info->name, "spl:mig") != 0 || info->init_func == NULL || info->close_func == NULL) return false;
/* Check if spl:mig is available (sysver equal to or greater than 4.0.0). */
return hosversionAtLeast(4, 0, 0);
}

View file

@ -85,25 +85,25 @@ void sha3ContextUpdate(Sha3Context *ctx, const void *src, size_t size)
LOG_MSG("Invalid parameters!");
return;
}
const u8 *src_u8 = (u8*)src;
size_t remaining = size;
/* Process we have anything buffered. */
if (ctx->buffered_bytes > 0)
{
/* Determine how much we can copy. */
const size_t copy_size = MIN(ctx->block_size - ctx->buffered_bytes, remaining);
/* Mix the bytes into our state. */
u8 *dst = (((u8*)ctx->internal_state) + ctx->buffered_bytes);
for(size_t i = 0; i < copy_size; ++i) dst[i] ^= src_u8[i];
/* Advance. */
src_u8 += copy_size;
remaining -= copy_size;
ctx->buffered_bytes += copy_size;
/* Process a block, if we filled one. */
if (ctx->buffered_bytes == ctx->block_size)
{
@ -111,20 +111,20 @@ void sha3ContextUpdate(Sha3Context *ctx, const void *src, size_t size)
ctx->buffered_bytes = 0;
}
}
/* Process blocks, if we have any. */
while(remaining >= ctx->block_size)
{
/* Mix the bytes into our state. */
u8 *dst = (u8*)ctx->internal_state;
for(size_t i = 0; i < ctx->block_size; ++i) dst[i] ^= src_u8[i];
sha3ProcessBlock(ctx);
src_u8 += ctx->block_size;
remaining -= ctx->block_size;
}
/* Copy any leftover data to our buffer. */
if (remaining > 0)
{
@ -141,14 +141,14 @@ void sha3ContextGetHash(Sha3Context *ctx, void *dst)
LOG_MSG("Invalid parameters!");
return;
}
/* If we need to, process the last block. */
if (!ctx->finalized)
{
sha3ProcessLastBlock(ctx);
ctx->finalized = true;
}
/* Copy the output hash. */
memcpy(dst, ctx->internal_state, ctx->hash_size);
}
@ -166,7 +166,7 @@ static u64 rotl_u64(u64 x, int s)
{
int N = (sizeof(u64) * 8);
int r = (s % N);
if (r == 0)
{
return x;
@ -175,7 +175,7 @@ static u64 rotl_u64(u64 x, int s)
{
return ((x << r) | (x >> (N - r)));
}
return rotr_u64(x, -r);
}
@ -183,7 +183,7 @@ static u64 rotr_u64(u64 x, int s)
{
int N = (sizeof(u64) * 8);
int r = (s % N);
if (r == 0)
{
return x;
@ -192,7 +192,7 @@ static u64 rotr_u64(u64 x, int s)
{
return ((x >> r) | (x << (N - r)));
}
return rotl_u64(x, -r);
}
@ -203,9 +203,9 @@ static void sha3ContextCreate(Sha3Context *out, u32 hash_size)
LOG_MSG("Invalid parameters!");
return;
}
memset(out, 0, sizeof(Sha3Context));
out->hash_size = SHA3_HASH_SIZE_BYTES(hash_size);
out->block_size = SHA3_BLOCK_SIZE(hash_size);
}
@ -213,7 +213,7 @@ static void sha3ContextCreate(Sha3Context *out, u32 hash_size)
static void sha3ProcessBlock(Sha3Context *ctx)
{
u64 tmp = 0, C[5] = {0};
/* Perform all rounds. */
for(u8 round = 0; round < SHA3_NUM_ROUNDS; ++round)
{
@ -222,13 +222,13 @@ static void sha3ProcessBlock(Sha3Context *ctx)
{
C[i] = (ctx->internal_state[i] ^ ctx->internal_state[i + 5] ^ ctx->internal_state[i + 10] ^ ctx->internal_state[i + 15] ^ ctx->internal_state[i + 20]);
}
for(size_t i = 0; i < 5; ++i)
{
tmp = (C[(i + 4) % 5] ^ rotl_u64(C[(i + 1) % 5], 1));
for(size_t j = 0; j < 5; ++j) ctx->internal_state[(5 * j) + i] ^= tmp;
}
/* Handle rho/pi. */
tmp = ctx->internal_state[1];
for(size_t i = 0; i < SHA3_NUM_ROUNDS; ++i)
@ -238,14 +238,14 @@ static void sha3ProcessBlock(Sha3Context *ctx)
ctx->internal_state[rho_next_idx] = rotl_u64(tmp, g_rhoShiftBit[i]);
tmp = C[0];
}
/* Handle chi. */
for(size_t i = 0; i < 5; ++i)
{
for(size_t j = 0; j < 5; ++j) C[j] = ctx->internal_state[(5 * i) + j];
for(size_t j = 0; j < 5; ++j) ctx->internal_state[(5 * i) + j] ^= ((~C[(j + 1) % 5]) & C[(j + 2) % 5]);
}
/* Handle iota. */
ctx->internal_state[0] ^= g_iotaRoundConstant[round];
}
@ -255,10 +255,10 @@ static void sha3ProcessLastBlock(Sha3Context *ctx)
{
/* Mix final bits (011) into our state. */
((u8*)ctx->internal_state)[ctx->buffered_bytes] ^= 0b110;
/* Mix in the high bit of the last word in our block. */
ctx->internal_state[(ctx->block_size / sizeof(u64)) - 1] ^= g_finalMask;
/* Process the last block. */
sha3ProcessBlock(ctx);
}

View file

@ -107,20 +107,20 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gam
LOG_MSG("Invalid parameters!");
return false;
}
u8 key_generation = id->c[0xF];
TikCommonBlock *tik_common_block = NULL;
/* Check if this ticket has already been retrieved. */
if (dst->type > TikType_None && dst->type <= TikType_SigHmac160 && dst->size >= SIGNED_TIK_MIN_SIZE && dst->size <= SIGNED_TIK_MAX_SIZE)
{
tik_common_block = tikGetCommonBlock(dst->data);
if (tik_common_block && !memcmp(tik_common_block->rights_id.c, id->c, 0x10)) return true;
}
/* Clear output ticket. */
memset(dst, 0, sizeof(Ticket));
/* Retrieve ticket data. */
bool tik_retrieved = (use_gamecard ? tikRetrieveTicketFromGameCardByRightsId(dst, id) : tikRetrieveTicketFromEsSaveDataByRightsId(dst, id));
if (!tik_retrieved)
@ -128,109 +128,109 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gam
LOG_MSG("Unable to retrieve ticket data!");
return false;
}
/* Get encrypted titlekey from ticket. */
if (!tikGetEncryptedTitleKeyFromTicket(dst))
{
LOG_MSG("Unable to retrieve encrypted titlekey from ticket!");
return false;
}
/* Get common ticket block. */
tik_common_block = tikGetCommonBlock(dst->data);
/* Get proper key generation value. */
/* Nintendo didn't start putting the key generation value into the rights ID until HOS 3.0.1. */
/* If this is the case, we'll just use the key generation value from the common ticket block. */
/* However, old custom tools used to wipe the key generation field or save its value to a different offset, so this may fail with titles with custom/modified tickets. */
if (key_generation < NcaKeyGeneration_Since301NUP || key_generation > NcaKeyGeneration_Max) key_generation = tik_common_block->key_generation;
/* Get decrypted titlekey. */
if (!tikGetDecryptedTitleKey(dst->dec_titlekey, dst->enc_titlekey, key_generation))
{
LOG_MSG("Unable to decrypt titlekey!");
return false;
}
/* Generate rights ID string. */
utilsGenerateHexStringFromData(dst->rights_id_str, sizeof(dst->rights_id_str), tik_common_block->rights_id.c, sizeof(tik_common_block->rights_id.c), false);
return true;
}
bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_chain, u64 *out_raw_cert_chain_size)
{
TikCommonBlock *tik_common_block = NULL;
u32 sig_type = 0;
u8 *signature = NULL;
u64 signature_size = 0;
bool dev_cert = false;
char cert_chain_issuer[0x40] = {0};
static const char *common_cert_names[] = { "XS00000020", "XS00000022", NULL };
u8 *raw_cert_chain = NULL;
u64 raw_cert_chain_size = 0;
if (!tik || tik->type == TikType_None || tik->type > TikType_SigHmac160 || tik->size < SIGNED_TIK_MIN_SIZE || tik->size > SIGNED_TIK_MAX_SIZE || \
!(tik_common_block = tikGetCommonBlock(tik->data)) || tik_common_block->titlekey_type != TikTitleKeyType_Personalized || !out_raw_cert_chain || !out_raw_cert_chain_size)
{
LOG_MSG("Invalid parameters!");
return false;
}
/* Generate raw certificate chain for the new signature issuer (common). */
dev_cert = (strstr(tik_common_block->issuer, "CA00000004") != NULL);
for(u8 i = 0; common_cert_names[i] != NULL; i++)
{
sprintf(cert_chain_issuer, "Root-CA%08X-%s", dev_cert ? 4 : 3, common_cert_names[i]);
raw_cert_chain = certGenerateRawCertificateChainBySignatureIssuer(cert_chain_issuer, &raw_cert_chain_size);
if (raw_cert_chain) break;
}
if (!raw_cert_chain)
{
LOG_MSG("Failed to generate raw certificate chain for common ticket signature issuer!");
return false;
}
/* Wipe signature. */
sig_type = signatureGetSigType(tik->data, false);
signature = signatureGetSig(tik->data);
signature_size = signatureGetSigSize(sig_type);
memset(signature, 0xFF, signature_size);
/* Change signature issuer. */
memset(tik_common_block->issuer, 0, sizeof(tik_common_block->issuer));
sprintf(tik_common_block->issuer, "%s", cert_chain_issuer);
/* Wipe the titlekey block and copy the encrypted titlekey to it. */
memset(tik_common_block->titlekey_block, 0, sizeof(tik_common_block->titlekey_block));
memcpy(tik_common_block->titlekey_block, tik->enc_titlekey, 0x10);
/* Update ticket size. */
tik->size = (signatureGetBlockSize(sig_type) + sizeof(TikCommonBlock));
/* Update the rest of the ticket fields. */
tik_common_block->titlekey_type = TikTitleKeyType_Common;
tik_common_block->property_mask &= ~(TikPropertyMask_ELicenseRequired | TikPropertyMask_Volatile);
tik_common_block->ticket_id = 0;
tik_common_block->device_id = 0;
tik_common_block->account_id = 0;
tik_common_block->sect_total_size = 0;
tik_common_block->sect_hdr_offset = (u32)tik->size;
tik_common_block->sect_hdr_count = 0;
tik_common_block->sect_hdr_entry_size = 0;
memset(tik->data + tik->size, 0, SIGNED_TIK_MAX_SIZE - tik->size);
/* Update output pointers. */
*out_raw_cert_chain = raw_cert_chain;
*out_raw_cert_chain_size = raw_cert_chain_size;
return true;
}
@ -241,37 +241,37 @@ static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsI
LOG_MSG("Invalid parameters!");
return false;
}
char tik_filename[0x30] = {0};
u64 tik_offset = 0, tik_size = 0;
utilsGenerateHexStringFromData(tik_filename, sizeof(tik_filename), id->c, sizeof(id->c), false);
strcat(tik_filename, ".tik");
if (!gamecardGetHashFileSystemEntryInfoByName(GameCardHashFileSystemPartitionType_Secure, tik_filename, &tik_offset, &tik_size))
{
LOG_MSG("Error retrieving offset and size for \"%s\" entry in secure hash FS partition!", tik_filename);
return false;
}
if (tik_size < SIGNED_TIK_MIN_SIZE || tik_size > SIGNED_TIK_MAX_SIZE)
{
LOG_MSG("Invalid size for \"%s\"! (0x%lX).", tik_filename, tik_size);
return false;
}
if (!gamecardReadStorage(dst->data, tik_size, tik_offset))
{
LOG_MSG("Failed to read \"%s\" data from the inserted gamecard!", tik_filename);
return false;
}
if (!tikGetTicketTypeAndSize(dst->data, tik_size, &(dst->type), &(dst->size)))
{
LOG_MSG("Unable to determine ticket type and size!");
return false;
}
return true;
}
@ -282,81 +282,81 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
LOG_MSG("Invalid parameters!");
return false;
}
u8 titlekey_type = 0;
save_ctx_t *save_ctx = NULL;
u64 buf_size = (SIGNED_TIK_MAX_SIZE * 0x100);
u8 *buf = NULL;
u64 ticket_offset = 0;
bool success = false;
/* Allocate memory to retrieve the ticket. */
if (!(buf = malloc(buf_size)))
{
LOG_MSG("Unable to allocate 0x%lX bytes block for temporary read buffer!", buf_size);
return false;
}
/* Get titlekey type. */
if (!tikGetTitleKeyTypeFromRightsId(id, &titlekey_type))
{
LOG_MSG("Unable to retrieve ticket titlekey type!");
goto end;
}
/* Open ES common/personalized system savefile. */
if (!(save_ctx = save_open_savefile(titlekey_type == TikTitleKeyType_Common ? TIK_COMMON_SAVEFILE_PATH : TIK_PERSONALIZED_SAVEFILE_PATH, 0)))
{
LOG_MSG("Failed to open ES %s ticket system savefile!", g_tikTitleKeyTypeStrings[titlekey_type]);
goto end;
}
/* Get ticket entry offset from ticket_list.bin. */
if (!tikGetTicketEntryOffsetFromTicketList(save_ctx, buf, buf_size, id, &ticket_offset, titlekey_type))
{
LOG_MSG("Unable to find an entry with a matching Rights ID in \"%s\" from ES %s ticket system save!", TIK_LIST_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
goto end;
}
/* Get ticket entry from ticket.bin. */
if (!tikRetrieveTicketEntryFromTicketBin(save_ctx, buf, buf_size, id, ticket_offset, titlekey_type))
{
LOG_MSG("Unable to find a matching %s ticket entry for the provided Rights ID!", g_tikTitleKeyTypeStrings[titlekey_type]);
goto end;
}
/* Get ticket type and size. */
if (!tikGetTicketTypeAndSize(buf, SIGNED_TIK_MAX_SIZE, &(dst->type), &(dst->size)))
{
LOG_MSG("Unable to determine ticket type and size!");
goto end;
}
memcpy(dst->data, buf, dst->size);
success = true;
end:
if (save_ctx) save_close_savefile(save_ctx);
if (buf) free(buf);
return success;
}
static bool tikGetEncryptedTitleKeyFromTicket(Ticket *tik)
{
TikCommonBlock *tik_common_block = NULL;
if (!tik || !(tik_common_block = tikGetCommonBlock(tik->data)))
{
LOG_MSG("Invalid parameters!");
return false;
}
switch(tik_common_block->titlekey_type)
{
case TikTitleKeyType_Common:
@ -372,7 +372,7 @@ static bool tikGetEncryptedTitleKeyFromTicket(Ticket *tik)
LOG_MSG("Invalid titlekey type value! (0x%02X).", tik_common_block->titlekey_type);
return false;
}
return true;
}
@ -383,20 +383,20 @@ static bool tikGetDecryptedTitleKey(void *dst, const void *src, u8 key_generatio
LOG_MSG("Invalid parameters!");
return false;
}
const u8 *ticket_common_key = NULL;
Aes128Context titlekey_aes_ctx = {0};
ticket_common_key = keysGetTicketCommonKey(key_generation);
if (!ticket_common_key)
{
LOG_MSG("Unable to retrieve ticket common key for key generation 0x%02X!", key_generation);
return false;
}
aes128ContextCreate(&titlekey_aes_ctx, ticket_common_key, false);
aes128DecryptBlock(&titlekey_aes_ctx, dst, src);
return true;
}
@ -407,24 +407,24 @@ static bool tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out)
LOG_MSG("Invalid parameters!");
return false;
}
u32 count = 0;
FsRightsId *rights_ids = NULL;
bool found = false;
for(u8 i = 0; i < 2; i++)
{
count = 0;
rights_ids = NULL;
if (!tikRetrieveRightsIdsByTitleKeyType(&rights_ids, &count, i == 1))
{
LOG_MSG("Unable to retrieve %s rights IDs!", g_tikTitleKeyTypeStrings[i]);
continue;
}
if (!count) continue;
for(u32 j = 0; j < count; j++)
{
if (!memcmp(rights_ids[j].c, id->c, 0x10))
@ -434,12 +434,12 @@ static bool tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out)
break;
}
}
free(rights_ids);
if (found) break;
}
return found;
}
@ -450,35 +450,35 @@ static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count,
LOG_MSG("Invalid parameters!");
return false;
}
Result rc = 0;
u32 count = 0, ids_written = 0;
FsRightsId *rights_ids = NULL;
u8 str_idx = (personalized ? TikTitleKeyType_Personalized : TikTitleKeyType_Common);
*out = NULL;
*out_count = 0;
rc = (personalized ? esCountPersonalizedTicket((s32*)&count) : esCountCommonTicket((s32*)&count));
if (R_FAILED(rc))
{
LOG_MSG("esCount%c%sTicket failed! (0x%08X).", toupper(g_tikTitleKeyTypeStrings[str_idx][0]), g_tikTitleKeyTypeStrings[str_idx] + 1, rc);
return false;
}
if (!count)
{
LOG_MSG("No %s tickets available!", g_tikTitleKeyTypeStrings[str_idx]);
return true;
}
rights_ids = calloc(count, sizeof(FsRightsId));
if (!rights_ids)
{
LOG_MSG("Unable to allocate memory for %s rights IDs!", g_tikTitleKeyTypeStrings[str_idx]);
return false;
}
rc = (personalized ? esListPersonalizedTicket((s32*)&ids_written, rights_ids, (s32)count) : esListCommonTicket((s32*)&ids_written, rights_ids, (s32)count));
if (R_FAILED(rc) || !ids_written)
{
@ -486,10 +486,10 @@ static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count,
free(rights_ids);
return false;
}
*out = rights_ids;
*out_count = ids_written;
return true;
}
@ -500,54 +500,54 @@ static bool tikGetTicketEntryOffsetFromTicketList(save_ctx_t *save_ctx, u8 *buf,
LOG_MSG("Invalid parameters!");
return false;
}
allocation_table_storage_ctx_t fat_storage = {0};
u64 ticket_list_bin_size = 0, br = 0, total_br = 0;
u8 last_rights_id[0x10];
memset(last_rights_id, 0xFF, sizeof(last_rights_id));
bool last_entry_found = false, success = false;
/* Get FAT storage info for the ticket_list.bin stored within the opened system savefile. */
if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, TIK_LIST_STORAGE_PATH, &fat_storage, &ticket_list_bin_size))
{
LOG_MSG("Failed to locate \"%s\" in ES %s ticket system save!", TIK_LIST_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
return false;
}
/* Check ticket_list.bin size. */
if (ticket_list_bin_size < sizeof(TikListEntry) || (ticket_list_bin_size % sizeof(TikListEntry)) != 0)
{
LOG_MSG("Invalid size for \"%s\" in ES %s ticket system save! (0x%lX).", TIK_LIST_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type], ticket_list_bin_size);
return false;
}
/* Look for an entry matching our rights ID in ticket_list.bin. */
while(total_br < ticket_list_bin_size)
{
if (buf_size > (ticket_list_bin_size - total_br)) buf_size = (ticket_list_bin_size - total_br);
if ((br = save_allocation_table_storage_read(&fat_storage, buf, total_br, buf_size)) != buf_size)
{
LOG_MSG("Failed to read 0x%lX bytes chunk at offset 0x%lX from \"%s\" in ES %s ticket system save!", buf_size, total_br, TIK_LIST_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
break;
}
for(u64 i = 0; i < buf_size; i += sizeof(TikListEntry))
{
if ((buf_size - i) < sizeof(TikListEntry)) break;
u64 entry_offset = (total_br + i);
TikListEntry *entry = (TikListEntry*)(buf + i);
/* Check if we found the last entry. */
if (!memcmp(entry->rights_id.c, last_rights_id, sizeof(last_rights_id)))
{
last_entry_found = true;
break;
}
/* Check if this is the entry we're looking for. */
if (!memcmp(entry->rights_id.c, id->c, sizeof(id->c)))
{
@ -557,12 +557,12 @@ static bool tikGetTicketEntryOffsetFromTicketList(save_ctx_t *save_ctx, u8 *buf,
break;
}
}
total_br += br;
if (last_entry_found || success) break;
}
return success;
}
@ -573,31 +573,31 @@ static bool tikRetrieveTicketEntryFromTicketBin(save_ctx_t *save_ctx, u8 *buf, u
LOG_MSG("Invalid parameters!");
return false;
}
allocation_table_storage_ctx_t fat_storage = {0};
u64 ticket_bin_size = 0, br = 0;
TikCommonBlock *tik_common_block = NULL;
Aes128CtrContext ctr_ctx = {0};
u8 null_ctr[AES_128_KEY_SIZE] = {0}, ctr[AES_128_KEY_SIZE] = {0}, dec_tik[SIGNED_TIK_MAX_SIZE] = {0};
bool is_volatile = false, success = false;
/* Get FAT storage info for the ticket.bin stored within the opened system savefile. */
if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, TIK_DB_STORAGE_PATH, &fat_storage, &ticket_bin_size))
{
LOG_MSG("Failed to locate \"%s\" in ES %s ticket system save!", TIK_DB_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
return false;
}
/* Check ticket.bin size. */
if (ticket_bin_size < SIGNED_TIK_MIN_SIZE || (ticket_bin_size % SIGNED_TIK_MAX_SIZE) != 0 || ticket_bin_size < (ticket_offset + SIGNED_TIK_MAX_SIZE))
{
LOG_MSG("Invalid size for \"%s\" in ES %s ticket system save! (0x%lX).", TIK_DB_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type], ticket_bin_size);
return false;
}
/* Read ticket data. */
if ((br = save_allocation_table_storage_read(&fat_storage, buf, ticket_offset, SIGNED_TIK_MAX_SIZE)) != SIGNED_TIK_MAX_SIZE)
{
@ -605,46 +605,46 @@ static bool tikRetrieveTicketEntryFromTicketBin(save_ctx_t *save_ctx, u8 *buf, u
g_tikTitleKeyTypeStrings[titlekey_type]);
return false;
}
/* Check if we're dealing with a volatile (encrypted) ticket. */
if (!(tik_common_block = tikGetCommonBlock(buf)) || strncmp(tik_common_block->issuer, "Root-", 5) != 0)
{
tik_common_block = NULL;
is_volatile = true;
/* Don't proceed if HOS version isn't at least 9.0.0. */
if (!hosversionAtLeast(9, 0, 0))
{
LOG_MSG("Unable to retrieve ES key entry for volatile tickets under HOS versions below 9.0.0!");
return false;
}
/* Retrieve ES program memory. */
if (!memRetrieveFullProgramMemory(&g_esMemoryLocation))
{
LOG_MSG("Failed to retrieve ES program memory!");
return false;
}
/* Retrieve the CTR key/IV from ES program memory in order to decrypt this ticket. */
for(u64 i = 0; i < g_esMemoryLocation.data_size; i += ES_CTRKEY_ENTRY_ALIGNMENT)
{
if ((g_esMemoryLocation.data_size - i) < (sizeof(TikEsCtrKeyEntry9x) * 2)) break;
/* Check if the key indexes are valid. idx2 should always be an odd number equal to idx + 1. */
TikEsCtrKeyPattern9x *pattern = (TikEsCtrKeyPattern9x*)(g_esMemoryLocation.data + i);
if (pattern->idx2 != (pattern->idx1 + 1) || !(pattern->idx2 & 1)) continue;
/* Check if the key is not null and if the CTR is. */
TikEsCtrKeyEntry9x *key_entry = (TikEsCtrKeyEntry9x*)pattern;
if (!memcmp(key_entry->key, null_ctr, sizeof(null_ctr)) || memcmp(key_entry->ctr, null_ctr, sizeof(null_ctr)) != 0) continue;
/* Check if we can decrypt the current ticket with this data. */
memset(&ctr_ctx, 0, sizeof(Aes128CtrContext));
aes128CtrInitializePartialCtr(ctr, key_entry->ctr, ticket_offset);
aes128CtrContextCreate(&ctr_ctx, key_entry->key, ctr);
aes128CtrCrypt(&ctr_ctx, dec_tik, buf, SIGNED_TIK_MAX_SIZE);
/* Check if we successfully decrypted this ticket. */
if ((tik_common_block = tikGetCommonBlock(dec_tik)) != NULL && !strncmp(tik_common_block->issuer, "Root-", 5))
{
@ -652,10 +652,10 @@ static bool tikRetrieveTicketEntryFromTicketBin(save_ctx_t *save_ctx, u8 *buf, u
tik_common_block = tikGetCommonBlock(buf);
break;
}
tik_common_block = NULL;
}
/* Check if we were able to decrypt the ticket. */
if (!tik_common_block)
{
@ -663,13 +663,13 @@ static bool tikRetrieveTicketEntryFromTicketBin(save_ctx_t *save_ctx, u8 *buf, u
goto end;
}
}
/* Check if the rights ID from the ticket common block matches the one we're looking for. */
if (!(success = (memcmp(tik_common_block->rights_id.c, id->c, 0x10) == 0))) LOG_MSG("Retrieved ticket doesn't hold a matching Rights ID!");
end:
if (is_volatile) memFreeMemoryLocation(&g_esMemoryLocation);
return success;
}
@ -678,21 +678,21 @@ static bool tikGetTicketTypeAndSize(void *data, u64 data_size, u8 *out_type, u64
u32 sig_type = 0;
u64 signed_ticket_size = 0;
u8 type = TikType_None;
if (!data || data_size < SIGNED_TIK_MIN_SIZE || data_size > SIGNED_TIK_MAX_SIZE || !out_type || !out_size)
{
LOG_MSG("Invalid parameters!");
return false;
}
if (!(signed_ticket_size = tikGetSignedTicketSize(data)) || signed_ticket_size > data_size)
{
LOG_MSG("Input buffer doesn't hold a valid signed ticket!");
return false;
}
sig_type = signatureGetSigType(data, false);
switch(sig_type)
{
case SignatureType_Rsa4096Sha1:
@ -713,9 +713,9 @@ static bool tikGetTicketTypeAndSize(void *data, u64 data_size, u8 *out_type, u64
default:
break;
}
*out_type = type;
*out_size = signed_ticket_size;
return true;
}

File diff suppressed because it is too large Load diff

View file

@ -44,12 +44,12 @@ static void umsFreeDeviceData(void);
bool umsInitialize(void)
{
bool ret = false;
SCOPED_LOCK(&g_umsMutex)
{
ret = g_umsInterfaceInit;
if (ret) break;
/* Initialize USB Mass Storage Host interface. */
Result rc = usbHsFsInitialize(0);
if (R_FAILED(rc))
@ -57,20 +57,20 @@ bool umsInitialize(void)
LOG_MSG("usbHsFsInitialize failed! (0x%08X).", rc);
break;
}
/* Get USB Mass Storage status change event. */
g_umsStatusChangeEvent = usbHsFsGetStatusChangeUserEvent();
/* Create user-mode exit event. */
ueventCreate(&g_umsDetectionThreadExitEvent, true);
/* Create USB Mass Storage detection thread. */
if (!(g_umsDetectionThreadCreated = umsCreateDetectionThread())) break;
/* Update flags. */
ret = g_umsInterfaceInit = true;
}
return ret;
}
@ -84,10 +84,10 @@ void umsExit(void)
umsDestroyDetectionThread();
g_umsDetectionThreadCreated = false;
}
/* Close USB Mass Storage Host interface. */
usbHsFsExit();
/* Update flag. */
g_umsInterfaceInit = false;
}
@ -96,21 +96,21 @@ void umsExit(void)
bool umsIsDeviceInfoUpdated(void)
{
bool ret = false;
SCOPED_TRY_LOCK(&g_umsMutex)
{
if (!g_umsInterfaceInit || !g_umsDeviceInfoUpdated) break;
ret = true;
g_umsDeviceInfoUpdated = false;
}
return ret;
}
UsbHsFsDevice *umsGetDevices(u32 *out_count)
{
UsbHsFsDevice *devices = NULL;
SCOPED_LOCK(&g_umsMutex)
{
if (!g_umsInterfaceInit || !out_count)
@ -118,14 +118,14 @@ UsbHsFsDevice *umsGetDevices(u32 *out_count)
LOG_MSG("Invalid parameters!");
break;
}
if (!g_umsDeviceCount || !g_umsDevices)
{
/* Update output device count. */
*out_count = 0;
break;
}
/* Allocate memory for the output devices. */
devices = calloc(g_umsDeviceCount, sizeof(UsbHsFsDevice));
if (!devices)
@ -133,14 +133,14 @@ UsbHsFsDevice *umsGetDevices(u32 *out_count)
LOG_MSG("Failed to allocate memory for %u devices!", g_umsDeviceCount);
break;
}
/* Copy device data. */
memcpy(devices, g_umsDevices, g_umsDeviceCount * sizeof(UsbHsFsDevice));
/* Update output device count. */
*out_count = g_umsDeviceCount;
}
return devices;
}
@ -151,7 +151,7 @@ static bool umsCreateDetectionThread(void)
LOG_MSG("Failed to create USB Mass Storage detection thread!");
return false;
}
return true;
}
@ -159,7 +159,7 @@ static void umsDestroyDetectionThread(void)
{
/* Signal the exit event to terminate the USB Mass Storage detection thread. */
ueventSignal(&g_umsDetectionThreadExitEvent);
/* Wait for the USB Mass Storage detection thread to exit. */
utilsJoinThread(&g_umsDetectionThread);
}
@ -167,36 +167,36 @@ static void umsDestroyDetectionThread(void)
static void umsDetectionThreadFunc(void *arg)
{
(void)arg;
Result rc = 0;
int idx = 0;
u32 listed_device_count = 0;
Waiter status_change_event_waiter = waiterForUEvent(g_umsStatusChangeEvent);
Waiter exit_event_waiter = waiterForUEvent(&g_umsDetectionThreadExitEvent);
while(true)
{
/* Wait until an event is triggered. */
rc = waitMulti(&idx, -1, status_change_event_waiter, exit_event_waiter);
if (R_FAILED(rc)) continue;
/* Exit event triggered. */
if (idx == 1) break;
SCOPED_LOCK(&g_umsMutex)
{
/* Free USB Mass Storage device data. */
umsFreeDeviceData();
/* Get mounted device count. */
g_umsDeviceCount = usbHsFsGetMountedDeviceCount();
LOG_MSG("USB Mass Storage status change event triggered! Mounted USB Mass Storage device count: %u.", g_umsDeviceCount);
if (g_umsDeviceCount)
{
bool fail = false;
/* Allocate mounted devices buffer. */
g_umsDevices = calloc(g_umsDeviceCount, sizeof(UsbHsFsDevice));
if (g_umsDevices)
@ -222,7 +222,7 @@ static void umsDetectionThreadFunc(void *arg)
LOG_MSG("Failed to allocate memory for mounted USB Mass Storage devices buffer!");
fail = true;
}
/* Free USB Mass Storage device data if something went wrong. */
if (fail) umsFreeDeviceData();
} else {
@ -231,10 +231,10 @@ static void umsDetectionThreadFunc(void *arg)
}
}
}
/* Free USB Mass Storage device data. */
umsFreeDeviceData();
threadExit();
}
@ -246,7 +246,7 @@ static void umsFreeDeviceData(void)
free(g_umsDevices);
g_umsDevices = NULL;
}
/* Reset device count. */
g_umsDeviceCount = 0;
}

File diff suppressed because it is too large Load diff

View file

@ -32,30 +32,30 @@ namespace nxdt::views
this->label->setHorizontalAlign(NVG_ALIGN_CENTER);
this->label->setParent(this);
}
ErrorFrame::~ErrorFrame(void)
{
delete this->label;
}
void ErrorFrame::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
{
nvgSave(vg);
/* Background. */
nvgFillColor(vg, brls::Application::getTheme()->backgroundColorRGB);
nvgBeginPath(vg);
nvgRect(vg, x, y, width, height);
nvgFill(vg);
/* Scale. */
float scale = (this->alpha + 2.0f) / 3.0f;
nvgTranslate(vg, (1.0f - scale) * width * 0.5f, (1.0f - scale) * height * 0.5f);
nvgScale(vg, scale, scale);
/* Label. */
this->label->frame(ctx);
/* [!] box. */
unsigned boxSize = style->CrashFrame.boxSize;
nvgStrokeColor(vg, RGB(255, 255, 255));
@ -63,32 +63,32 @@ namespace nxdt::views
nvgBeginPath(vg);
nvgRect(vg, x + (width - boxSize) / 2, y + style->CrashFrame.boxSpacing, boxSize, boxSize);
nvgStroke(vg);
nvgFillColor(vg, RGB(255, 255, 255));
nvgFontSize(vg, (float)style->CrashFrame.boxSize / 1.25f);
nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
nvgBeginPath(vg);
nvgText(vg, x + width / 2, y + style->CrashFrame.boxSpacing + boxSize / 2, "!", nullptr);
nvgFill(vg);
/* End scale. */
nvgResetTransform(vg);
nvgRestore(vg);
}
void ErrorFrame::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
{
this->label->setWidth(roundf(static_cast<float>(this->width) * 0.90f));
this->label->invalidate(true);
this->label->setBoundaries(
this->x + (this->width - this->label->getWidth()) / 2,
this->y + (this->height - style->AppletFrame.footerHeight) / 2,
this->label->getWidth(),
this->label->getHeight());
}
void ErrorFrame::SetMessage(std::string msg)
{
this->label->setText(msg);

View file

@ -6,7 +6,7 @@
*
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
* Loosely based on debug_helpers.cpp from EdiZon-Rewrite.
*
*
* nxdumptool is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@ -51,16 +51,16 @@ namespace nxdt::utils {
LOG_MSG("Invalid parameters!");
return;
}
u32 p = 0;
Result rc = 0;
/* Find the memory region in which this function is stored. */
/* The start of it will be the base address the homebrew was mapped to. */
rc = svcQueryMemory(out, &p, static_cast<u64>(reinterpret_cast<uintptr_t>(&GetHomebrewMemoryInfo)));
if (R_FAILED(rc)) LOG_MSG("svcQueryMemory failed! (0x%08X).", rc);
}
static bool UnwindStack(u64 *out_stack_trace, u32 *out_stack_trace_size, size_t max_stack_trace_size, u64 cur_fp)
{
if (!out_stack_trace || !out_stack_trace_size || !max_stack_trace_size || !cur_fp)
@ -68,28 +68,28 @@ namespace nxdt::utils {
LOG_MSG("Invalid parameters!");
return false;
}
struct StackFrame {
u64 fp; ///< Frame Pointer (pointer to previous stack frame).
u64 lr; ///< Link Register (return address).
};
*out_stack_trace_size = 0;
u64 fp_base = (cur_fp & FP_MASK);
for(size_t i = 0; i < max_stack_trace_size; i++)
{
/* Last check fixes a crash while dealing with certain stack frame addresses. */
if (!cur_fp || (cur_fp % sizeof(u64)) || (cur_fp & FP_MASK) != fp_base) break;
auto cur_trace = reinterpret_cast<StackFrame*>(cur_fp);
out_stack_trace[(*out_stack_trace_size)++] = cur_trace->lr;
cur_fp = cur_trace->fp;
}
return (*out_stack_trace_size > 0);
}
static void NORETURN AbortProgramExecution(std::string str)
{
if (g_borealisInitialized)
@ -101,14 +101,14 @@ namespace nxdt::utils {
/* Print error message using console output. */
utilsPrintConsoleError(str.c_str());
}
/* Clean up resources. */
utilsCloseResources();
/* Exit application. */
__nx_applet_exit_mode = 1;
exit(EXIT_FAILURE);
__builtin_unreachable();
}
}
@ -119,30 +119,30 @@ extern "C" {
{
/* Log error. */
LOG_MSG("*** libnx aborted with error code: 0x%08X ***", res);
/* Abort program execution. */
std::string crash_str = (g_borealisInitialized ? i18n::getStr("generic/libnx_abort"_i18n, res) : fmt::format("Fatal error triggered in libnx!\nError code: 0x{:08X}.", res));
nxdt::utils::AbortProgramExecution(crash_str);
}
/* libnx exception handler override. */
void __libnx_exception_handler(ThreadExceptionDump *ctx)
{
MemoryInfo info = {0};
u32 stack_trace_size = 0;
u64 stack_trace[STACK_TRACE_SIZE] = {0};
char *exception_str = NULL;
size_t exception_str_size = 0;
std::string error_desc_str, crash_str;
/* Get homebrew memory info. */
nxdt::utils::GetHomebrewMemoryInfo(&info);
/* Log exception type. */
LOG_MSG("*** Exception Triggered ***");
switch(ctx->error_desc)
{
case ThreadExceptionDesc_InstructionAbort:
@ -170,54 +170,54 @@ extern "C" {
error_desc_str = "Unknown";
break;
}
EH_ADD_FMT_STR("Type: %s (0x%X)\r\n", error_desc_str.c_str(), ctx->error_desc);
/* Log CPU registers. */
EH_ADD_FMT_STR("Registers:");
for(size_t i = 0; i < MAX_ELEMENTS(ctx->cpu_gprs); i++)
{
u64 reg = ctx->cpu_gprs[i].x;
EH_ADD_FMT_STR("\r\n X%02lu: 0x%lX", i, reg);
if (IS_HB_ADDR(reg)) EH_ADD_FMT_STR(" (BASE + 0x%lX)", reg - info.addr);
}
EH_ADD_FMT_STR("\r\n FP: 0x%lX", ctx->fp.x);
if (IS_HB_ADDR(ctx->fp.x)) EH_ADD_FMT_STR(" (BASE + 0x%lX)", ctx->fp.x - info.addr);
EH_ADD_FMT_STR("\r\n LR: 0x%lX", ctx->lr.x);
if (IS_HB_ADDR(ctx->lr.x)) EH_ADD_FMT_STR(" (BASE + 0x%lX)", ctx->lr.x - info.addr);
EH_ADD_FMT_STR("\r\n SP: 0x%lX", ctx->sp.x);
if (IS_HB_ADDR(ctx->sp.x)) EH_ADD_FMT_STR(" (BASE + 0x%lX)", ctx->sp.x - info.addr);
EH_ADD_FMT_STR("\r\n PC: 0x%lX", ctx->pc.x);
if (IS_HB_ADDR(ctx->pc.x)) EH_ADD_FMT_STR(" (BASE + 0x%lX)", ctx->pc.x - info.addr);
EH_ADD_FMT_STR("\r\n");
/* Unwind stack. */
if (nxdt::utils::UnwindStack(stack_trace, &stack_trace_size, STACK_TRACE_SIZE, ctx->fp.x))
{
/* Log stack trace. */
EH_ADD_FMT_STR("Stack Trace:");
for(u32 i = 0; i < stack_trace_size; i++)
{
u64 addr = stack_trace[i];
EH_ADD_FMT_STR("\r\n [%02u]: 0x%lX", stack_trace_size - i - 1, addr);
if (IS_HB_ADDR(addr)) EH_ADD_FMT_STR(" (BASE + 0x%lX)", addr - info.addr);
}
EH_ADD_FMT_STR("\r\n");
}
/* Write log string. */
logWriteStringToLogFile(exception_str);
/* Free exception info string. */
if (exception_str) free(exception_str);
/* Abort program execution. */
crash_str = (g_borealisInitialized ? i18n::getStr("generic/exception_triggered"_i18n, error_desc_str, ctx->error_desc) : \
fmt::format("Fatal exception triggered!\nReason: {} (0x{:X}).", error_desc_str, ctx->error_desc));

View file

@ -50,13 +50,13 @@ DRESULT disk_read (
)
{
(void)pdrv;
Result rc = 0;
u64 start_offset = ((u64)FF_MAX_SS * (u64)sector);
u64 read_size = ((u64)FF_MAX_SS * (u64)count);
rc = fsStorageRead(utilsGetEmmcBisSystemPartitionStorage(), start_offset, buff, read_size);
return (R_SUCCEEDED(rc) ? RES_OK : RES_ERROR);
}

View file

@ -32,10 +32,10 @@ namespace nxdt::views
/* Set custom spacing. */
this->list->setSpacing(this->list->getSpacing() / 2);
this->list->setMarginBottom(20);
/* Gamecard properties table. */
this->list->addView(new brls::Header("gamecard_tab/list/properties_table/header"_i18n));
this->properties_table = new FocusableTable();
this->capacity = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/capacity"_i18n);
this->total_size = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/total_size"_i18n);
@ -45,38 +45,38 @@ namespace nxdt::views
this->sdk_version = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/sdk_version"_i18n);
this->compatibility_type = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/compatibility_type"_i18n);
this->list->addView(this->properties_table);
/* ListItem elements. */
this->list->addView(new brls::Header("gamecard_tab/list/dump_options"_i18n));
this->dump_card_image = new brls::ListItem("gamecard_tab/list/dump_card_image/label"_i18n, "gamecard_tab/list/dump_card_image/description"_i18n);
/*this->dump_card_image->getClickEvent()->subscribe([](brls::View *view) {
});*/
this->list->addView(this->dump_card_image);
this->dump_certificate = new brls::ListItem("gamecard_tab/list/dump_certificate/label"_i18n, "gamecard_tab/list/dump_certificate/description"_i18n);
this->list->addView(this->dump_certificate);
this->dump_header = new brls::ListItem("gamecard_tab/list/dump_header/label"_i18n, "gamecard_tab/list/dump_header/description"_i18n);
this->list->addView(this->dump_header);
this->dump_decrypted_cardinfo = new brls::ListItem("gamecard_tab/list/dump_decrypted_cardinfo/label"_i18n, "gamecard_tab/list/dump_decrypted_cardinfo/description"_i18n);
this->list->addView(this->dump_decrypted_cardinfo);
this->dump_initial_data = new brls::ListItem("gamecard_tab/list/dump_initial_data/label"_i18n, "gamecard_tab/list/dump_initial_data/description"_i18n);
this->list->addView(this->dump_initial_data);
this->dump_hfs_partitions = new brls::ListItem("gamecard_tab/list/dump_hfs_partitions/label"_i18n, "gamecard_tab/list/dump_hfs_partitions/description"_i18n);
this->list->addView(this->dump_hfs_partitions);
/* Subscribe to gamecard status event. */
this->gc_status_task_sub = this->root_view->RegisterGameCardTaskListener([this](GameCardStatus gc_status) {
/* Switch to the error layer if gamecard info hasn't been loaded. */
if (gc_status < GameCardStatus_InsertedAndInfoLoaded) this->SwitchLayerView(true);
switch(gc_status)
{
case GameCardStatus_NotInserted:
@ -99,54 +99,54 @@ namespace nxdt::views
/* Fill properties table. */
GameCardInfo card_info = {0};
gamecardGetDecryptedCardInfoArea(&card_info);
this->capacity->setValue(this->GetFormattedSizeString(&gamecardGetRomCapacity));
this->total_size->setValue(this->GetFormattedSizeString(&gamecardGetTotalSize));
this->trimmed_size->setValue(this->GetFormattedSizeString(&gamecardGetTrimmedSize));
const Version *upp_version = &(card_info.upp_version);
this->update_version->setValue(fmt::format("{}.{}.{}-{}.{} (v{})", upp_version->system_version.major, upp_version->system_version.minor, upp_version->system_version.micro, \
upp_version->system_version.major_relstep, upp_version->system_version.minor_relstep, upp_version->value));
u64 fw_version = card_info.fw_version;
this->lafw_version->setValue(fmt::format("{} ({})", fw_version, fw_version >= GameCardFwVersion_Count ? "generic/unknown"_i18n : gamecardGetRequiredHosVersionString(fw_version)));
const SdkAddOnVersion *fw_mode = &(card_info.fw_mode);
this->sdk_version->setValue(fmt::format("{}.{}.{}-{} (v{})", fw_mode->major, fw_mode->minor, fw_mode->micro, fw_mode->relstep, fw_mode->value));
u8 compatibility_type = card_info.compatibility_type;
this->compatibility_type->setValue(fmt::format("{} ({})", \
compatibility_type >= GameCardCompatibilityType_Count ? "generic/unknown"_i18n : gamecardGetCompatibilityTypeString(compatibility_type), \
compatibility_type));
/* Switch to the list view. */
this->SwitchLayerView(false);
break;
}
default:
break;
}
/* Update internal gamecard status. */
this->gc_status = gc_status;
});
}
GameCardTab::~GameCardTab(void)
{
/* Unregister task listener. */
this->root_view->UnregisterGameCardTaskListener(this->gc_status_task_sub);
}
std::string GameCardTab::GetFormattedSizeString(GameCardSizeFunc func)
{
u64 size = 0;
char strbuf[0x40] = {0};
func(&size);
utilsGenerateFormattedSizeString(static_cast<double>(size), strbuf, sizeof(strbuf));
return std::string(strbuf);
}
}

View file

@ -28,22 +28,22 @@ namespace nxdt::views
/* Error frame. */
this->error_frame = new ErrorFrame(msg);
this->addLayer(this->error_frame);
/* List. */
this->list = new brls::List();
this->addLayer(this->list);
}
void LayeredErrorFrame::SetParentSidebarItem(brls::SidebarItem *sidebar_item)
{
if (sidebar_item) this->sidebar_item = sidebar_item;
}
bool LayeredErrorFrame::IsListItemFocused(void)
{
brls::View *cur_view = brls::Application::getCurrentFocus();
size_t cur_list_count = this->list->getViewsCount();
if (cur_list_count)
{
while(cur_view)
@ -52,19 +52,19 @@ namespace nxdt::views
cur_view = cur_view->getParent();
}
}
return false;
}
int LayeredErrorFrame::GetFocusStackViewIndex(void)
{
size_t cur_list_count = this->list->getViewsCount();
std::vector<brls::View*> *focus_stack = brls::Application::getFocusStack();
if (cur_list_count && focus_stack)
{
size_t focus_stack_size = focus_stack->size();
for(size_t i = 0; i < focus_stack_size; i++)
{
for(size_t j = 0; j < cur_list_count; j++)
@ -73,49 +73,49 @@ namespace nxdt::views
}
}
}
return -1;
}
bool LayeredErrorFrame::UpdateFocusStackViewAtIndex(int index, brls::View *view)
{
std::vector<brls::View*> *focus_stack = brls::Application::getFocusStack();
if (!focus_stack || index < 0) return false;
size_t focus_stack_size = focus_stack->size();
if (index >= static_cast<int>(focus_stack_size)) return false;
focus_stack->at(index) = view;
brls::Logger::debug("Focus stack updated");
return true;
}
void LayeredErrorFrame::SwitchLayerView(bool use_error_frame, bool update_focused_view, bool update_focus_stack)
{
int cur_index = this->getLayerIndex();
int new_index = (use_error_frame ? 0 : 1);
size_t cur_list_count = this->list->getViewsCount();
brls::View *first_child = nullptr;
int focus_stack_index = this->GetFocusStackViewIndex();
bool focus_stack_updated = false;
if (cur_list_count)
{
/* Get pointer to the first list item. */
first_child = this->list->getChild(0);
/* Update focus stack information, if needed. */
if (update_focus_stack && focus_stack_index > -1) focus_stack_updated = this->UpdateFocusStackViewAtIndex(focus_stack_index, use_error_frame ? this->sidebar_item : first_child);
}
if (!focus_stack_updated)
{
/* Check if the user is currently focusing a list item. */
if (!update_focused_view) update_focused_view = this->IsListItemFocused();
if (update_focused_view)
{
/* Update focused view. */
@ -126,13 +126,13 @@ namespace nxdt::views
} else {
/* Move focus to the first list item. */
brls::Application::giveFocus(first_child);
/* Make sure to call willAppear() on our list to update the scrolling accordingly. */
this->list->willAppear(true);
}
}
}
/* Change layer view only if the new index is different. */
if (cur_index != new_index)
{

View file

@ -31,24 +31,24 @@ int main(int argc, char *argv[])
{
/* Set scope guard to clean up resources at exit. */
ON_SCOPE_EXIT { utilsCloseResources(); };
/* Initialize application resources. */
if (!utilsInitializeResources(argc, (const char**)argv)) return EXIT_FAILURE;
/* Set Borealis log level. */
/* TODO: rework this before release. */
brls::Logger::setLogLevel(brls::LogLevel::DEBUG);
/* Load Borealis translation files. */
brls::i18n::loadTranslations();
/* Set common footer. */
brls::Application::setCommonFooter("v" APP_VERSION " (" GIT_REV ")");
/* Initialize Borealis. */
if (!brls::Application::init(APP_TITLE)) return EXIT_FAILURE;
g_borealisInitialized = true;
/* Check if we're running under applet mode. */
if (utilsAppletModeCheck())
{
@ -61,10 +61,10 @@ int main(int argc, char *argv[])
/* Push root view. */
brls::Application::pushView(new nxdt::views::RootView());
}
/* Run the application. */
while(brls::Application::mainLoop());
/* Exit. */
return EXIT_SUCCESS;
}

View file

@ -35,32 +35,32 @@ namespace nxdt::views
{
this->progress_display = new brls::ProgressDisplay();
this->progress_display->setParent(this);
this->size_lbl = new brls::Label(brls::LabelStyle::MEDIUM, "", false);
this->size_lbl->setVerticalAlign(NVG_ALIGN_BOTTOM);
this->size_lbl->setParent(this);
this->speed_eta_lbl = new brls::Label(brls::LabelStyle::MEDIUM, "", false);
this->speed_eta_lbl->setVerticalAlign(NVG_ALIGN_TOP);
this->speed_eta_lbl->setParent(this);
}
OptionsTabUpdateProgress::~OptionsTabUpdateProgress(void)
{
delete this->progress_display;
delete this->size_lbl;
delete this->speed_eta_lbl;
}
void OptionsTabUpdateProgress::SetProgress(const nxdt::tasks::DownloadTaskProgress& progress)
{
/* Update progress percentage. */
this->progress_display->setProgress(progress.percentage, 100);
/* Update size string. */
this->size_lbl->setText(fmt::format("{} / {}", this->GetFormattedSizeString(static_cast<double>(progress.current)), \
progress.size ? this->GetFormattedSizeString(static_cast<double>(progress.size)) : "?"));
/* Update speed / ETA string. */
if (progress.eta.length())
{
@ -68,158 +68,158 @@ namespace nxdt::views
} else {
this->speed_eta_lbl->setText(fmt::format("{}/s", this->GetFormattedSizeString(progress.speed)));
}
this->invalidate();
}
void OptionsTabUpdateProgress::willAppear(bool resetState)
{
this->progress_display->willAppear(resetState);
}
void OptionsTabUpdateProgress::willDisappear(bool resetState)
{
this->progress_display->willDisappear(resetState);
}
void OptionsTabUpdateProgress::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
{
/* Progress display. */
this->progress_display->frame(ctx);
/* Size label. */
this->size_lbl->frame(ctx);
/* Speed / ETA label. */
this->speed_eta_lbl->frame(ctx);
}
void OptionsTabUpdateProgress::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
{
unsigned elem_width = roundf(static_cast<float>(this->width) * 0.90f);
/* Progress display. */
this->progress_display->setBoundaries(
this->x + (this->width - elem_width) / 2,
this->y + (this->height - style->CrashFrame.buttonHeight) / 2,
elem_width,
style->CrashFrame.buttonHeight);
this->progress_display->invalidate(true);
/* Size label. */
this->size_lbl->setWidth(elem_width);
this->size_lbl->invalidate(true);
this->size_lbl->setBoundaries(
this->x + (this->width - this->size_lbl->getWidth()) / 2,
this->progress_display->getY() - this->progress_display->getHeight() / 8,
this->size_lbl->getWidth(),
this->size_lbl->getHeight());
/* Speed / ETA label. */
this->speed_eta_lbl->setWidth(elem_width);
this->speed_eta_lbl->invalidate(true);
this->speed_eta_lbl->setBoundaries(
this->x + (this->width - this->speed_eta_lbl->getWidth()) / 2,
this->progress_display->getY() + this->progress_display->getHeight() + this->progress_display->getHeight() / 8,
this->speed_eta_lbl->getWidth(),
this->speed_eta_lbl->getHeight());
}
std::string OptionsTabUpdateProgress::GetFormattedSizeString(double size)
{
char strbuf[0x40] = {0};
utilsGenerateFormattedSizeString(size, strbuf, sizeof(strbuf));
return std::string(strbuf);
}
OptionsTabUpdateFileDialog::OptionsTabUpdateFileDialog(std::string path, std::string url, bool force_https, std::string success_str) : brls::Dialog(), success_str(success_str)
{
/* Set content view. */
OptionsTabUpdateProgress *update_progress = new OptionsTabUpdateProgress();
this->setContentView(update_progress);
/* Add cancel button. */
this->addButton("options_tab/update_dialog/cancel"_i18n, [this](brls::View* view) {
/* Cancel download task. */
this->download_task.cancel();
/* Close dialog. */
this->close();
});
/* Disable cancelling with B button. */
this->setCancelable(false);
/* Subscribe to the download task. */
this->download_task.RegisterListener([this, update_progress](const nxdt::tasks::DownloadTaskProgress& progress) {
/* Update progress. */
update_progress->SetProgress(progress);
/* Check if the download task has finished. */
if (this->download_task.isFinished())
{
/* Stop spinner. */
update_progress->willDisappear();
/* Update button label. */
this->setButtonText(0, "options_tab/update_dialog/close"_i18n);
/* Display notification. */
brls::Application::notify(this->download_task.get() ? this->success_str : "options_tab/notifications/update_failed"_i18n);
}
});
/* Start download task. */
this->download_task.execute(path, url, force_https);
}
OptionsTabUpdateApplicationFrame::OptionsTabUpdateApplicationFrame(void) : brls::StagedAppletFrame(false)
{
/* Set UI properties. */
this->setTitle("options_tab/update_app/label"_i18n);
this->setIcon(BOREALIS_ASSET("icon/" APP_TITLE ".jpg"));
/* Add first stage. */
this->wait_lbl = new brls::Label(brls::LabelStyle::DIALOG, "options_tab/update_app/frame/please_wait"_i18n, false);
this->wait_lbl->setHorizontalAlign(NVG_ALIGN_CENTER);
this->addStage(this->wait_lbl);
/* Add second stage. */
this->changelog_list = new brls::List();
this->changelog_list->setSpacing(this->changelog_list->getSpacing() / 2);
this->changelog_list->setMarginBottom(20);
this->addStage(this->changelog_list);
/* Add third stage. */
this->update_progress = new OptionsTabUpdateProgress();
this->addStage(this->update_progress);
/* Register cancel action. */
this->registerAction("brls/hints/back"_i18n, brls::Key::B, [this](void) {
return this->onCancel();
});
/* Subscribe to the global focus change event so we can rebuild hints as soon as this frame is pushed to the view stack. */
this->focus_event_sub = brls::Application::getGlobalFocusChangeEvent()->subscribe([this](brls::View* view) {
this->rebuildHints();
});
/* Subscribe to the JSON task. */
this->json_task.RegisterListener([this](const nxdt::tasks::DownloadTaskProgress& progress) {
/* Return immediately if the JSON task hasn't finished. */
if (!this->json_task.isFinished()) return;
bool pop_view = false;
std::string notification = "";
/* Retrieve task result. */
nxdt::tasks::DownloadDataResult json_task_result = this->json_task.get();
this->json_buf = json_task_result.first;
this->json_buf_size = json_task_result.second;
/* Parse downloaded JSON object. */
if (utilsParseGitHubReleaseJsonData(this->json_buf, this->json_buf_size, &(this->json_data)))
{
@ -231,52 +231,52 @@ namespace nxdt::views
} else {
/* Update flag. */
pop_view = true;
/* Set notification string. */
notification = "options_tab/notifications/up_to_date"_i18n;
}
} else {
/* Log downloaded data. */
LOG_DATA(this->json_buf, this->json_buf_size, "Failed to parse GitHub release JSON. Downloaded data:");
/* Update flag. */
pop_view = true;
/* Set notification string. */
notification = "options_tab/notifications/github_json_failed"_i18n;
}
/* Pop view (if needed). */
if (pop_view)
{
/* Display notification. */
brls::Application::notify(notification);
/* Pop view. */
this->onCancel();
}
});
/* Start JSON task. */
this->json_task.execute(GITHUB_API_RELEASE_URL, true);
}
OptionsTabUpdateApplicationFrame::~OptionsTabUpdateApplicationFrame(void)
{
/* Free parsed JSON data. */
utilsFreeGitHubReleaseJsonData(&(this->json_data));
/* Free JSON buffer. */
if (this->json_buf) free(this->json_buf);
/* Unsubscribe focus event listener. */
brls::Application::getGlobalFocusChangeEvent()->unsubscribe(this->focus_event_sub);
}
void OptionsTabUpdateApplicationFrame::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
{
brls::StagedAppletFrame::layout(vg, style, stash);
if (this->getCurrentStage() == 0)
{
/* Center wait label. */
@ -284,40 +284,40 @@ namespace nxdt::views
this->wait_lbl->invalidate();
}
}
bool OptionsTabUpdateApplicationFrame::onCancel(void)
{
/* Cancel NRO task. */
this->nro_task.cancel();
/* Cancel JSON task. */
this->json_task.cancel();
/* Pop view. */
brls::Application::popView(brls::ViewAnimation::SLIDE_RIGHT);
return true;
}
void OptionsTabUpdateApplicationFrame::DisplayChangelog(void)
{
std::string item;
std::stringstream ss(std::string(this->json_data.changelog));
/* Display version string at the top. */
FocusableLabel *version_lbl = new FocusableLabel(brls::LabelStyle::CRASH, std::string(this->json_data.version), true);
version_lbl->setHorizontalAlign(NVG_ALIGN_CENTER);
this->changelog_list->addView(version_lbl);
/* Display release date and commit hash. */
brls::Label *release_details_lbl = new brls::Label(brls::LabelStyle::DESCRIPTION, i18n::getStr("options_tab/update_app/frame/release_details"_i18n, \
this->json_data.commit_hash, RootView::GetFormattedDateString(this->json_data.date)), true);
release_details_lbl->setHorizontalAlign(NVG_ALIGN_CENTER);
this->changelog_list->addView(release_details_lbl);
/* Add changelog header. */
this->changelog_list->addView(new brls::Header("options_tab/update_app/frame/changelog_header"_i18n));
/* Split changelog string and fill list. */
while(std::getline(ss, item))
{
@ -325,7 +325,7 @@ namespace nxdt::views
/* Make sure to remove any possible carriage returns. */
size_t item_len = item.length();
if (!item_len) continue;
if (item.back() == '\r')
{
if (item_len > 1)
@ -335,123 +335,123 @@ namespace nxdt::views
continue;
}
}
/* Add line to the changelog view. */
this->changelog_list->addView(new FocusableLabel(brls::LabelStyle::SMALL, item, true));
}
/* Register update action. */
this->registerAction("options_tab/update_app/frame/update_action"_i18n, brls::Key::PLUS, [this](void) {
/* Display update progress. */
this->DisplayUpdateProgress();
return true;
});
/* Rebuild action hints. */
this->rebuildHints();
/* Go to the next stage. */
this->nextStage();
}
void OptionsTabUpdateApplicationFrame::DisplayUpdateProgress(void)
{
/* Remove update action. */
this->registerAction("options_tab/update_app/frame/update_action"_i18n, brls::Key::PLUS, [](void) {
return true;
}, true);
/* Register cancel action once more, using a different label. */
this->registerAction("options_tab/update_dialog/cancel"_i18n, brls::Key::B, [this](void) {
return this->onCancel();
});
/* Rebuild action hints. */
this->rebuildHints();
/* Subscribe to the NRO task. */
this->nro_task.RegisterListener([this](const nxdt::tasks::DownloadTaskProgress& progress) {
/* Update progress. */
this->update_progress->SetProgress(progress);
/* Check if the download task has finished. */
if (this->nro_task.isFinished())
{
/* Get NRO task result and immediately set application updated state if the task succeeded. */
bool ret = this->nro_task.get();
if (ret) utilsSetApplicationUpdatedState();
/* Display notification. */
brls::Application::notify(ret ? "options_tab/notifications/app_updated"_i18n : "options_tab/notifications/update_failed"_i18n);
/* Pop view */
this->onCancel();
}
});
/* Start NRO task. */
this->nro_task.execute(NRO_TMP_PATH, std::string(this->json_data.download_url), true);
/* Go to the next stage. */
this->nextStage();
}
OptionsTab::OptionsTab(RootView *root_view) : brls::List(), root_view(root_view)
{
/* Set custom spacing. */
this->setSpacing(this->getSpacing() / 2);
this->setMarginBottom(20);
/* Information about actual dump options. */
brls::Label *dump_options_info = new brls::Label(brls::LabelStyle::DESCRIPTION, "options_tab/dump_options_info"_i18n, true);
dump_options_info->setHorizontalAlign(NVG_ALIGN_CENTER);
this->addView(dump_options_info);
/* Overclock. */
brls::ToggleListItem *overclock = new brls::ToggleListItem("options_tab/overclock/label"_i18n, configGetBoolean("overclock"), \
"options_tab/overclock/description"_i18n, "options_tab/overclock/value_enabled"_i18n, \
"options_tab/overclock/value_disabled"_i18n);
overclock->getClickEvent()->subscribe([](brls::View* view) {
brls::ToggleListItem *item = static_cast<brls::ToggleListItem*>(view);
/* Get current value. */
bool value = item->getToggleState();
/* Change hardware clocks based on the current value. */
utilsOverclockSystem(value);
/* Update configuration. */
configSetBoolean("overclock", value);
brls::Logger::debug("Overclock setting changed by user.");
});
this->addView(overclock);
/* Naming convention. */
brls::SelectListItem *naming_convention = new brls::SelectListItem("options_tab/naming_convention/label"_i18n, {
"options_tab/naming_convention/value_00"_i18n,
"options_tab/naming_convention/value_01"_i18n
}, static_cast<unsigned>(configGetInteger("naming_convention")),
"options_tab/naming_convention/description"_i18n);
naming_convention->getValueSelectedEvent()->subscribe([](int selected) {
/* Make sure the current value isn't out of bounds. */
if (selected < 0 || selected > static_cast<int>(TitleNamingConvention_Count)) return;
/* Update configuration. */
configSetInteger("naming_convention", selected);
brls::Logger::debug("Naming convention setting changed by user.");
});
this->addView(naming_convention);
/* Update NSWDB XML. */
brls::ListItem *update_nswdb_xml = new brls::ListItem("options_tab/update_nswdb_xml/label"_i18n, "options_tab/update_nswdb_xml/description"_i18n);
update_nswdb_xml->getClickEvent()->subscribe([this](brls::View* view) {
if (!this->root_view->IsInternetConnectionAvailable())
{
@ -459,17 +459,17 @@ namespace nxdt::views
this->DisplayNotification("options_tab/notifications/no_internet_connection"_i18n);
return;
}
/* Open update dialog. */
OptionsTabUpdateFileDialog *dialog = new OptionsTabUpdateFileDialog(NSWDB_XML_PATH, NSWDB_XML_URL, false, "options_tab/notifications/nswdb_xml_updated"_i18n);
dialog->open(false);
});
this->addView(update_nswdb_xml);
/* Update application. */
brls::ListItem *update_app = new brls::ListItem("options_tab/update_app/label"_i18n, "options_tab/update_app/description"_i18n);
update_app->getClickEvent()->subscribe([this](brls::View* view) {
if (envIsNso())
{
@ -489,31 +489,31 @@ namespace nxdt::views
this->DisplayNotification("options_tab/notifications/already_updated"_i18n);
return;
}
/* Display update frame. */
brls::Application::pushView(new OptionsTabUpdateApplicationFrame(), brls::ViewAnimation::SLIDE_LEFT, false);
});
this->addView(update_app);
}
OptionsTab::~OptionsTab(void)
{
brls::menu_timer_kill(&(this->notification_timer));
}
void OptionsTab::DisplayNotification(std::string str)
{
if (str == "" || !this->display_notification) return;
brls::Application::notify(str);
this->display_notification = false;
this->notification_timer_ctx.duration = brls::Application::getStyle()->AnimationDuration.notificationTimeout;
this->notification_timer_ctx.cb = [this](void *userdata) { this->display_notification = true; };
this->notification_timer_ctx.tick = [](void*){};
this->notification_timer_ctx.userdata = nullptr;
brls::menu_timer_start(&(this->notification_timer), &(this->notification_timer_ctx));
}
}

View file

@ -34,132 +34,132 @@ namespace nxdt::views
RootView::RootView(void) : brls::TabFrame()
{
int material = brls::Application::getFontStash()->material;
/* Set UI properties. */
this->setTitle(APP_TITLE);
this->setIcon(BOREALIS_ASSET("icon/" APP_TITLE ".jpg"));
/* Check if we're running under applet mode. */
this->applet_mode = utilsAppletModeCheck();
/* Create labels. */
this->applet_mode_lbl = new brls::Label(brls::LabelStyle::HINT, "root_view/applet_mode"_i18n);
this->applet_mode_lbl->setColor(nvgRGB(255, 0, 0));
this->applet_mode_lbl->setFontSize(brls::Application::getStyle()->AppletFrame.titleSize);
this->applet_mode_lbl->setParent(this);
this->time_lbl = new brls::Label(brls::LabelStyle::SMALL, "");
this->time_lbl->setHorizontalAlign(NVG_ALIGN_RIGHT);
this->time_lbl->setVerticalAlign(NVG_ALIGN_TOP);
this->time_lbl->setParent(this);
this->battery_icon = new brls::Label(brls::LabelStyle::SMALL, "");
this->battery_icon->setFont(material);
this->battery_icon->setHorizontalAlign(NVG_ALIGN_RIGHT);
this->battery_icon->setVerticalAlign(NVG_ALIGN_TOP);
this->battery_icon->setParent(this);
this->battery_percentage = new brls::Label(brls::LabelStyle::SMALL, "");
this->battery_percentage->setHorizontalAlign(NVG_ALIGN_RIGHT);
this->battery_percentage->setVerticalAlign(NVG_ALIGN_TOP);
this->battery_percentage->setParent(this);
this->connection_icon = new brls::Label(brls::LabelStyle::SMALL, "");
this->connection_icon->setFont(material);
this->connection_icon->setHorizontalAlign(NVG_ALIGN_RIGHT);
this->connection_icon->setVerticalAlign(NVG_ALIGN_TOP);
this->connection_icon->setParent(this);
this->connection_status_lbl = new brls::Label(brls::LabelStyle::SMALL, "");
this->connection_status_lbl->setHorizontalAlign(NVG_ALIGN_RIGHT);
this->connection_status_lbl->setVerticalAlign(NVG_ALIGN_TOP);
this->connection_status_lbl->setParent(this);
this->usb_icon = new brls::Label(brls::LabelStyle::SMALL, "\uE1E0");
this->usb_icon->setFont(material);
this->usb_icon->setHorizontalAlign(NVG_ALIGN_RIGHT);
this->usb_icon->setVerticalAlign(NVG_ALIGN_TOP);
this->usb_icon->setParent(this);
this->usb_host_speed_lbl = new brls::Label(brls::LabelStyle::SMALL, "root_view/not_connected"_i18n);
this->usb_host_speed_lbl->setHorizontalAlign(NVG_ALIGN_RIGHT);
this->usb_host_speed_lbl->setVerticalAlign(NVG_ALIGN_TOP);
this->usb_host_speed_lbl->setParent(this);
/* Start background tasks. */
this->status_info_task = new nxdt::tasks::StatusInfoTask();
this->gc_status_task = new nxdt::tasks::GameCardTask();
this->title_task = new nxdt::tasks::TitleTask();
this->ums_task = new nxdt::tasks::UmsTask();
this->usb_host_task = new nxdt::tasks::UsbHostTask();
/* Add tabs. */
GameCardTab *gamecard_tab = new GameCardTab(this);
this->addTab("root_view/tabs/gamecard"_i18n, gamecard_tab);
gamecard_tab->SetParentSidebarItem(static_cast<brls::SidebarItem*>(this->sidebar->getChild(this->sidebar->getViewsCount() - 1)));
this->addSeparator();
TitlesTab *user_titles_tab = new TitlesTab(this, false);
this->addTab("root_view/tabs/user_titles"_i18n, user_titles_tab);
user_titles_tab->SetParentSidebarItem(static_cast<brls::SidebarItem*>(this->sidebar->getChild(this->sidebar->getViewsCount() - 1)));
TitlesTab *system_titles_tab = new TitlesTab(this, true);
this->addTab("root_view/tabs/system_titles"_i18n, system_titles_tab);
system_titles_tab->SetParentSidebarItem(static_cast<brls::SidebarItem*>(this->sidebar->getChild(this->sidebar->getViewsCount() - 1)));
this->addSeparator();
this->addTab("root_view/tabs/options"_i18n, new OptionsTab(this));
this->addSeparator();
this->addTab("root_view/tabs/about"_i18n, new AboutTab());
/* Subscribe to status info event. */
this->status_info_task_sub = this->status_info_task->RegisterListener([this](const nxdt::tasks::StatusInfoData *status_info_data) {
u32 charge_percentage = status_info_data->charge_percentage;
PsmChargerType charger_type = status_info_data->charger_type;
NifmInternetConnectionType connection_type = status_info_data->connection_type;
char *ip_addr = status_info_data->ip_addr;
/* Update time label. */
this->time_lbl->setText(this->GetFormattedDateString(status_info_data->timeinfo));
/* Update battery labels. */
this->battery_icon->setText(charger_type != PsmChargerType_Unconnected ? "\uE1A3" : (charge_percentage <= 15 ? "\uE19C" : "\uE1A4"));
this->battery_icon->setColor(charger_type != PsmChargerType_Unconnected ? nvgRGB(0, 255, 0) : (charge_percentage <= 15 ? nvgRGB(255, 0, 0) : brls::Application::getTheme()->textColor));
this->battery_percentage->setText(fmt::format("{}%", charge_percentage));
/* Update network labels. */
this->connection_icon->setText(!connection_type ? "\uE195" : (connection_type == NifmInternetConnectionType_WiFi ? "\uE63E" : "\uE8BE"));
this->connection_status_lbl->setText(ip_addr ? std::string(ip_addr) : "root_view/not_connected"_i18n);
});
/* Subscribe to USB host event. */
this->usb_host_task_sub = this->usb_host_task->RegisterListener([this](UsbHostSpeed usb_host_speed) {
/* Update USB host speed label. */
this->usb_host_speed_lbl->setText(usb_host_speed ? fmt::format("USB {}.0", usb_host_speed) : "root_view/not_connected"_i18n);
});
}
RootView::~RootView(void)
{
/* Unregister USB host task listener. */
this->usb_host_task->UnregisterListener(this->usb_host_task_sub);
/* Unregister status info task listener. */
this->status_info_task->UnregisterListener(this->status_info_task_sub);
/* Stop background tasks. */
this->status_info_task->stop();
this->gc_status_task->stop();
this->title_task->stop();
this->ums_task->stop();
this->usb_host_task->stop();
/* Destroy labels. */
delete this->applet_mode_lbl;
delete this->time_lbl;
@ -170,16 +170,16 @@ namespace nxdt::views
delete this->usb_icon;
delete this->usb_host_speed_lbl;
}
std::string RootView::GetFormattedDateString(const struct tm& timeinfo)
{
bool is_am = true;
struct tm ts = timeinfo;
/* Update time label. */
ts.tm_mon++;
ts.tm_year += 1900;
if ("generic/time_format"_i18n.compare("12") == 0)
{
/* Adjust time for 12-hour clock. */
@ -193,85 +193,85 @@ namespace nxdt::views
ts.tm_hour = 12;
}
}
return i18n::getStr("generic/date"_i18n, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, is_am ? "AM" : "PM");
}
void RootView::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
{
brls::AppletFrame::draw(vg, x, y, width, height, style, ctx);
if (this->applet_mode) this->applet_mode_lbl->frame(ctx);
this->time_lbl->frame(ctx);
this->battery_icon->frame(ctx);
this->battery_percentage->frame(ctx);
this->connection_icon->frame(ctx);
this->connection_status_lbl->frame(ctx);
this->usb_icon->frame(ctx);
this->usb_host_speed_lbl->frame(ctx);
}
void RootView::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
{
int x_pos = 0, y_pos = 0;
brls::AppletFrame::layout(vg, style, stash);
if (this->applet_mode)
{
/* Applet mode label. */
x_pos = (this->x + (this->width - this->applet_mode_lbl->getTextWidth()) / 2);
y_pos = (this->y + (style->AppletFrame.headerHeightRegular / 2) + style->AppletFrame.titleOffset);
this->applet_mode_lbl->setBoundaries(x_pos, y_pos, 0, 0);
this->applet_mode_lbl->invalidate();
}
/* Time label. */
x_pos = (this->x + this->width - style->AppletFrame.separatorSpacing - style->AppletFrame.footerTextSpacing);
y_pos = this->y + style->AppletFrame.imageTopPadding;
this->time_lbl->setBoundaries(x_pos, y_pos, 0, 0);
this->time_lbl->invalidate();
/* Battery stats and network connection labels. */
y_pos += (this->time_lbl->getTextHeight() + 5);
this->connection_status_lbl->setBoundaries(x_pos, y_pos, 0, 0);
this->connection_status_lbl->invalidate();
x_pos -= (5 + this->connection_status_lbl->getTextWidth());
this->connection_icon->setBoundaries(x_pos, y_pos, 0, 0);
this->connection_icon->invalidate();
x_pos -= (10 + this->connection_icon->getTextWidth());
this->battery_percentage->setBoundaries(x_pos, y_pos, 0, 0);
this->battery_percentage->invalidate();
x_pos -= (5 + this->battery_percentage->getTextWidth());
this->battery_icon->setBoundaries(x_pos, y_pos, 0, 0);
this->battery_icon->invalidate();
/* USB host speed labels. */
x_pos = (this->x + this->width - style->AppletFrame.separatorSpacing - style->AppletFrame.footerTextSpacing);
y_pos += (this->connection_status_lbl->getTextHeight() + 5);
this->usb_host_speed_lbl->setBoundaries(x_pos, y_pos, 0, 0);
this->usb_host_speed_lbl->invalidate();
x_pos -= (5 + this->usb_host_speed_lbl->getTextWidth());
this->usb_icon->setBoundaries(x_pos, y_pos, 0, 0);
this->usb_icon->invalidate();
}
brls::View* RootView::getDefaultFocus(void)
{
return this->sidebar->getChild(0);

View file

@ -30,41 +30,41 @@ using namespace brls::i18n::literals; /* For _i18n. */
namespace nxdt::tasks
{
/* Status info task. */
StatusInfoTask::StatusInfoTask(void) : brls::RepeatingTask(NXDT_TASK_INTERVAL)
{
brls::RepeatingTask::start();
brls::Logger::debug("Status info task started.");
}
StatusInfoTask::~StatusInfoTask(void)
{
brls::Logger::debug("Status info task stopped.");
}
bool StatusInfoTask::IsInternetConnectionAvailable(void)
{
return (this->status_info_data.ip_addr != NULL);
}
void StatusInfoTask::run(retro_time_t current_time)
{
brls::RepeatingTask::run(current_time);
StatusInfoData *status_info_data = &(this->status_info_data);
/* Get current time. */
time_t unix_time = time(NULL);
localtime_r(&unix_time, &(status_info_data->timeinfo));
/* Get battery stats. */
psmGetBatteryChargePercentage(&(status_info_data->charge_percentage));
psmGetChargerType(&(status_info_data->charger_type));
/* Get network connection status. */
u32 signal_strength = 0;
NifmInternetConnectionStatus connection_status = static_cast<NifmInternetConnectionStatus>(0);
Result rc = nifmGetInternetConnectionStatus(&(status_info_data->connection_type), &signal_strength, &connection_status);
if (R_SUCCEEDED(rc))
{
@ -80,35 +80,35 @@ namespace nxdt::tasks
status_info_data->connection_type = static_cast<NifmInternetConnectionType>(0);
status_info_data->ip_addr = NULL;
}
/* Fire task event. */
this->status_info_event.fire(status_info_data);
}
/* Gamecard task. */
GameCardTask::GameCardTask(void) : brls::RepeatingTask(NXDT_TASK_INTERVAL)
{
brls::RepeatingTask::start();
brls::Logger::debug("Gamecard task started.");
this->first_notification = (gamecardGetStatus() >= GameCardStatus_Processing);
}
GameCardTask::~GameCardTask(void)
{
brls::Logger::debug("Gamecard task stopped.");
}
void GameCardTask::run(retro_time_t current_time)
{
brls::RepeatingTask::run(current_time);
this->cur_gc_status = static_cast<GameCardStatus>(gamecardGetStatus());
if (this->cur_gc_status != this->prev_gc_status)
{
brls::Logger::debug("Gamecard status change triggered: {}.", this->cur_gc_status);
if (!this->first_notification)
{
if (this->prev_gc_status == GameCardStatus_NotInserted && this->cur_gc_status == GameCardStatus_Processing)
@ -126,165 +126,165 @@ namespace nxdt::tasks
} else {
this->first_notification = false;
}
/* Update previous gamecard status. */
this->prev_gc_status = this->cur_gc_status;
/* Fire task event. */
this->gc_status_event.fire(this->cur_gc_status);
}
}
/* Title task. */
TitleTask::TitleTask(void) : brls::RepeatingTask(NXDT_TASK_INTERVAL)
{
/* Get system metadata entries. */
this->PopulateApplicationMetadataVector(true);
/* Get user metadata entries. */
this->PopulateApplicationMetadataVector(false);
/* Start task. */
brls::RepeatingTask::start();
brls::Logger::debug("Title task started.");
}
TitleTask::~TitleTask(void)
{
/* Clear application metadata vectors. */
this->system_metadata.clear();
this->user_metadata.clear();
brls::Logger::debug("Title task stopped.");
}
void TitleTask::run(retro_time_t current_time)
{
brls::RepeatingTask::run(current_time);
if (titleIsGameCardInfoUpdated())
{
brls::Logger::debug("Title info updated.");
//brls::Application::notify("tasks/notifications/user_titles"_i18n);
/* Update user metadata vector. */
this->PopulateApplicationMetadataVector(false);
/* Fire task event. */
this->title_event.fire(&(this->user_metadata));
}
}
const TitleApplicationMetadataVector* TitleTask::GetApplicationMetadata(bool is_system)
{
return (is_system ? &(this->system_metadata) : &(this->user_metadata));
}
void TitleTask::PopulateApplicationMetadataVector(bool is_system)
{
TitleApplicationMetadata **app_metadata = NULL;
u32 app_metadata_count = 0;
/* Get pointer to output vector. */
TitleApplicationMetadataVector *vector = (is_system ? &(this->system_metadata) : &(this->user_metadata));
vector->clear();
/* Get application metadata entries. */
app_metadata = titleGetApplicationMetadataEntries(is_system, &app_metadata_count);
if (app_metadata)
{
/* Fill output vector. */
for(u32 i = 0; i < app_metadata_count; i++) vector->push_back(app_metadata[i]);
/* Free application metadata array. */
free(app_metadata);
}
brls::Logger::debug("Retrieved {} {} metadata {}.", app_metadata_count, is_system ? "system" : "user", app_metadata_count == 1 ? "entry" : "entries");
}
/* USB Mass Storage task. */
UmsTask::UmsTask(void) : brls::RepeatingTask(NXDT_TASK_INTERVAL)
{
brls::RepeatingTask::start();
brls::Logger::debug("UMS task started.");
}
UmsTask::~UmsTask(void)
{
/* Clear UMS device vector. */
this->ums_devices.clear();
brls::Logger::debug("UMS task stopped.");
}
void UmsTask::run(retro_time_t current_time)
{
brls::RepeatingTask::run(current_time);
if (umsIsDeviceInfoUpdated())
{
brls::Logger::debug("UMS device info updated.");
brls::Application::notify("tasks/notifications/ums_device"_i18n);
/* Update UMS device vector. */
this->PopulateUmsDeviceVector();
/* Fire task event. */
this->ums_event.fire(&(this->ums_devices));
}
}
void UmsTask::PopulateUmsDeviceVector(void)
{
UsbHsFsDevice *ums_devices = NULL;
u32 ums_device_count = 0;
/* Clear UMS device vector. */
this->ums_devices.clear();
/* Get UMS devices. */
ums_devices = umsGetDevices(&ums_device_count);
if (ums_devices)
{
/* Fill UMS device vector. */
for(u32 i = 0; i < ums_device_count; i++) this->ums_devices.push_back(ums_devices[i]);
/* Free UMS devices array. */
free(ums_devices);
}
brls::Logger::debug("Retrieved info for {} UMS {}.", ums_device_count, ums_device_count == 1 ? "device" : "devices");
}
/* USB host device connection task. */
UsbHostTask::UsbHostTask(void) : brls::RepeatingTask(NXDT_TASK_INTERVAL)
{
brls::RepeatingTask::start();
brls::Logger::debug("USB host task started.");
}
UsbHostTask::~UsbHostTask(void)
{
brls::Logger::debug("USB host task stopped.");
}
void UsbHostTask::run(retro_time_t current_time)
{
brls::RepeatingTask::run(current_time);
this->cur_usb_host_speed = static_cast<UsbHostSpeed>(usbIsReady());
if (this->cur_usb_host_speed != this->prev_usb_host_speed)
{
brls::Logger::debug("USB host speed changed: {}.", this->cur_usb_host_speed);
brls::Application::notify(this->cur_usb_host_speed ? "tasks/notifications/usb_host_connected"_i18n : "tasks/notifications/usb_host_disconnected"_i18n);
/* Update previous USB host speed. */
this->prev_usb_host_speed = this->cur_usb_host_speed;
/* Fire task event. */
this->usb_host_event.fire(this->cur_usb_host_speed);
}

View file

@ -31,7 +31,7 @@ namespace nxdt::views
{
u64 title_id = this->app_metadata->title_id;
bool user_ret = false;
if (!this->is_system)
{
/* Get user application data. */
@ -40,16 +40,16 @@ namespace nxdt::views
/* Get system title info. */
this->system_title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, title_id);
}
/* Make sure we got title information. */
if ((!this->is_system && !user_ret) || (this->is_system && !this->system_title_info)) throw fmt::format("Failed to retrieve title information for {:016X}.", title_id);
/* Add tabs. */
this->addTab("Red", new brls::Rectangle(nvgRGB(255, 0, 0)));
this->addTab("Green", new brls::Rectangle(nvgRGB(0, 255, 0)));
this->addTab("Blue", new brls::Rectangle(nvgRGB(0, 0, 255)));
}
TitlesTabPopup::~TitlesTabPopup(void)
{
/* Free title information. */
@ -60,26 +60,26 @@ namespace nxdt::views
titleFreeTitleInfo(&(this->system_title_info));
}
}
TitlesTabItem::TitlesTabItem(const TitleApplicationMetadata *app_metadata, bool is_system) : brls::ListItem(std::string(app_metadata->lang_entry.name), "", ""), \
app_metadata(app_metadata),
is_system(is_system)
{
/* Set sublabel. */
if (!this->is_system) this->setSubLabel(std::string(app_metadata->lang_entry.author));
/* Set thumbnail (if needed). */
if (app_metadata->icon && app_metadata->icon_size) this->setThumbnail(app_metadata->icon, app_metadata->icon_size);
/* Set value. */
this->setValue(fmt::format("{:016X}", this->app_metadata->title_id), false, false);
}
TitlesTab::TitlesTab(RootView *root_view, bool is_system) : LayeredErrorFrame("titles_tab/no_titles_available"_i18n), root_view(root_view), is_system(is_system)
{
/* Populate list. */
this->PopulateList(this->root_view->GetApplicationMetadata(this->is_system));
/* Subscribe to the title event if this is the user titles tab. */
if (!this->is_system)
{
@ -89,45 +89,45 @@ namespace nxdt::views
});
}
}
TitlesTab::~TitlesTab(void)
{
/* Unregister task listener if this is the user titles tab. */
if (!this->is_system) this->root_view->UnregisterTitleTaskListener(this->title_task_sub);
}
void TitlesTab::PopulateList(const nxdt::tasks::TitleApplicationMetadataVector* app_metadata)
{
/* Populate variables. */
size_t app_metadata_count = (app_metadata ? app_metadata->size() : 0);
bool update_focused_view = this->IsListItemFocused();
int focus_stack_index = this->GetFocusStackViewIndex();
/* If needed, switch to the error frame *before* cleaning up our list. */
if (!app_metadata_count) this->SwitchLayerView(true);
/* Clear list. */
this->list->clear();
this->list->invalidate(true);
/* Return immediately if we have no user application metadata. */
if (!app_metadata_count) return;
/* Populate list. */
for(TitleApplicationMetadata *cur_app_metadata : *app_metadata)
{
/* Create list item. */
TitlesTabItem *title = new TitlesTabItem(cur_app_metadata, this->is_system);
/* Register click event. */
title->getClickEvent()->subscribe([](brls::View *view) {
TitlesTabItem *item = static_cast<TitlesTabItem*>(view);
const TitleApplicationMetadata *app_metadata = item->GetApplicationMetadata();
bool is_system = item->IsSystemTitle();
/* Create popup. */
TitlesTabPopup *popup = nullptr;
try {
popup = new TitlesTabPopup(app_metadata, is_system);
} catch(const std::string& msg) {
@ -135,13 +135,13 @@ namespace nxdt::views
if (popup) delete popup;
return;
}
/* Display popup. */
std::string name = std::string(app_metadata->lang_entry.name);
std::string tid = fmt::format("{:016X}", app_metadata->title_id);
std::string sub_left = (!is_system ? std::string(app_metadata->lang_entry.author) : tid);
std::string sub_right = (!is_system ? tid : "");
if (app_metadata->icon && app_metadata->icon_size)
{
brls::PopupFrame::open(name, app_metadata->icon, app_metadata->icon_size, popup, sub_left, sub_right);
@ -149,14 +149,14 @@ namespace nxdt::views
brls::PopupFrame::open(name, popup, sub_left, sub_right);
}
});
/* Add list item to our view. */
this->list->addView(title);
}
/* Update focus stack, if needed. */
if (focus_stack_index > -1) this->UpdateFocusStackViewAtIndex(focus_stack_index, this->list->getChild(0));
/* Switch to the list. */
this->list->invalidate(true);
this->SwitchLayerView(false, update_focused_view, focus_stack_index < 0);

View file

@ -1,28 +1,28 @@
todo:
nca / nca_storage / bktr: re-test support for all section types
nca: add function to retrieve a pointer to a nca fs ctx based on section type? (e.g. like titleGetContentInfoByTypeAndIdOffset)
log: verbosity levels
log: nxlink output for advanced users
title: always retrieve names from unpacked nacps? (e.g. if an update changes the name of a title, like deltarune)
title: use dlc index as part of the output dump filename?
title: more functions for title lookup? (filters, patches / aoc, etc.)
title: more functions for content lookup? (based on id)
title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles
gamecard: functions to display filelist
pfs0: functions to display filelist
romfs: functions to display filelist
usb: change buffer size?
usb: change chunk size?
usb: improve abi (make it rest-like?)
usb: improve cancel mechanism
others: check todo with grep
others: dump verification via nswdb / no-intro
others: fatfs browser for emmc partitions
@ -30,40 +30,40 @@ todo:
reminder:
list of top level functions designed to alter nca data in order of (possible) usage:
out of dump loop:
* ncaSetDownloadDistributionType (instead of always using it like legacy, offer it as an option)
* ncaRemoveTitlekeyCrypto (can be used with digital titles + game updates in gamecards)
* nacpGenerateNcaPatch (Control)
* calls romfsGenerateFileEntryPatch
* calls ncaGenerateHierarchicalSha256Patch / ncaGenerateHierarchicalIntegrityPatch
* ncaEncryptHeader (doesn't modify anything per se, but it's used to generate new encrypted header data if needed)
inside dump loop:
* cnmtGenerateNcaPatch (Meta)
* calls pfsGenerateEntryPatch
* calls ncaGenerateHierarchicalSha256Patch
* returns true if cnmt needs no patching
* demands an immediate ncaEncryptHeader call
* ncaIsHeaderDirty (doesn't modify anything per se, but it's used to check if any of the functions above has been used, basically - and by extension, if the functions below need to be used)
* ncaWriteEncryptedHeaderDataToMemoryBuffer (write encrypted nca header data)
* cnmtWriteNcaPatch (writes cnmt patch)
* calls pfsWriteEntryPatchToMemoryBuffer
* calls ncaWriteHierarchicalSha256PatchToMemoryBuffer
* nacpWriteNcaPatch (writes nacp patch)
* calls romfsWriteFileEntryPatchToMemoryBuffer
* calls ncaWriteHierarchicalSha256PatchToMemoryBuffer / ncaWriteHierarchicalIntegrityPatchToMemoryBuffer
* cnmtUpdateContentInfo (used to update content entry info in the raw cnmt copy after dumping each one - ignores the current content if its a meta nca)
minor steps to take into account:
* check if rights_id_available == true and titlekey_retrieved == false (preload handling)
* actually, just inform the user about it - this is being handled