1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2025-01-03 14:26:01 +00:00

Fix building with latest libnx + QoL changes.

libnx now implements fsDeviceOperatorGetGameCardIdSet(), so I got rid of my own implementation.

Other changes include:

* cnmt: add cnmtVerifyContentHash().

* defines: add SHA256_HASH_STR_SIZE.

* fs_ext: add FsCardId1MakerCode, FsCardId1MemoryType and FsCardId2CardType enums.
* fs_ext: update FsCardId* structs.

* gamecard: change all package_id definitions from u64 -> u8[0x8].
* gamecard: fix misleading struct member names in GameCardHeader.
* gamecard: rename gamecardGetIdSet() -> gamecardGetCardIdSet().

* gamecard_tab: fix Package ID printing.
* gamecard_tab: add Card ID Set printing.

* host: add executable flag to Python scripts.

* keys: detect if we're dealing with a wiped eTicket RSA device key (e.g. via set:cal blanking). If so, the application will still launch, but all operations related to personalized titlekey crypto are disabled.

* pfs: rename PartitionFileSystemFileContext -> PartitionFileSystemImageContext and propagate the change throughout the codebase.
* pfs: rename PFS_FULL_HEADER_ALIGNMENT -> PFS_HEADER_PADDING_ALIGNMENT and update pfsWriteImageContextHeaderToMemoryBuffer() accordingly.

* poc: print certain button prompts with reversed colors, in the hopes of getting the user's attention.
* poc: NSP, Ticket and NCA submenus for updates and DLC updates now display the highest available title by default.
* poc: simplified output path generation for extracted NCA FS section dumps.
* poc: handle edge cases where a specific NCA from an update has no matching equivalent by type/ID offset in its base title (e.g. Fall Guys' HtmlDocument NCA).
* poc: implement NCA checksum validation while generating NSP dumps.

* romfs: update romfsInitializeContext() to allow its base_nca_fs_ctx argument to be NULL.

* usb: use USB_BOS_SIZE only once.

* workflow: update commit hash referenced by "rewrite-prerelease" tag on update.
This commit is contained in:
Pablo Curiel 2023-11-03 02:22:47 +01:00
parent ec6deede37
commit e1df86fb27
21 changed files with 360 additions and 177 deletions

View file

@ -63,8 +63,8 @@ jobs:
./build.sh ./build.sh
#- name: Build nxdumptool-rewrite GUI binary #- name: Build nxdumptool-rewrite GUI binary
# working-directory: nxdumptool
# run: | # run: |
# cd "$GITHUB_WORKSPACE/nxdumptool"
# make -j$(nproc) # make -j$(nproc)
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
@ -94,14 +94,15 @@ jobs:
- name: Upload artifact to prerelease - name: Upload artifact to prerelease
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1
with: with:
# only update on prerelease # Only update attachments on "rewrite-prerelease" tag. Make sure to update the commit referenced by the tag as well by using the branch name.
prerelease: True prerelease: True
tag: "rewrite-prerelease" tag: "rewrite-prerelease"
commit: "rewrite"
updateOnlyUnreleased: True updateOnlyUnreleased: True
# remove old artifacts & replace with new ones # Remove old artifacts and replace with new ones.
removeArtifacts: True removeArtifacts: True
replacesArtifacts: True replacesArtifacts: True
# update preference # Update preferences.
allowUpdates: True allowUpdates: True
omitBody: True omitBody: True
omitBodyDuringUpdate: True omitBodyDuringUpdate: True

View file

@ -143,6 +143,7 @@ static u64 utilsGetButtonsHeld(void);
static void utilsWaitForButtonPress(u64 flag); static void utilsWaitForButtonPress(u64 flag);
static void consolePrint(const char *text, ...); static void consolePrint(const char *text, ...);
static void consolePrintReversedColors(const char *text, ...);
static void consoleRefresh(void); static void consoleRefresh(void);
static u32 menuGetElementCount(const Menu *menu); static u32 menuGetElementCount(const Menu *menu);
@ -153,6 +154,8 @@ void updateStorageList(void);
void freeTitleList(Menu *menu); void freeTitleList(Menu *menu);
void updateTitleList(Menu *menu, Menu *submenu, bool is_system); void updateTitleList(Menu *menu, Menu *submenu, bool is_system);
static TitleInfo *getLatestTitleInfo(TitleInfo *title_info, u32 *out_idx, u32 *out_count);
void freeNcaList(void); void freeNcaList(void);
void updateNcaList(TitleInfo *title_info); void updateNcaList(TitleInfo *title_info);
static void switchNcaListTitle(Menu *cur_menu, u32 *element_count, TitleInfo *title_info); static void switchNcaListTitle(Menu *cur_menu, u32 *element_count, TitleInfo *title_info);
@ -966,8 +969,8 @@ int main(int argc, char *argv[])
{ {
if (cur_menu->id != MenuId_NcaFsSections && cur_menu->id != MenuId_NcaFsSectionsSubMenu && (title_info->previous || title_info->next)) if (cur_menu->id != MenuId_NcaFsSections && cur_menu->id != MenuId_NcaFsSectionsSubMenu && (title_info->previous || title_info->next))
{ {
consolePrint("press l/zl and/or r/zr to change the selected title\n"); consolePrintReversedColors("press l/zl/r/zr to change the selected title\n");
consolePrint("title: %u / %u\n", title_info_idx, title_info_count); consolePrintReversedColors("title: %u / %u\n", title_info_idx + 1, title_info_count);
consolePrint("______________________________\n\n"); consolePrint("______________________________\n\n");
} }
@ -988,7 +991,8 @@ int main(int argc, char *argv[])
if (cur_menu->id == MenuId_Nca) if (cur_menu->id == MenuId_Nca)
{ {
consolePrint("press y to switch to %s mode\n", g_ncaMenuRawMode ? "nca fs section" : "raw nca"); consolePrintReversedColors("current mode: %s\n", g_ncaMenuRawMode ? "raw nca" : "nca fs section");
consolePrintReversedColors("press y to switch to %s mode\n", g_ncaMenuRawMode ? "nca fs section" : "raw nca");
consolePrint("______________________________\n\n"); consolePrint("______________________________\n\n");
} }
@ -1123,6 +1127,8 @@ int main(int argc, char *argv[])
if (title_info) if (title_info)
{ {
title_info = getLatestTitleInfo(title_info, &title_info_idx, &title_info_count);
if (child_menu->id == MenuId_Nca) if (child_menu->id == MenuId_Nca)
{ {
updateNcaList(title_info); updateNcaList(title_info);
@ -1135,12 +1141,6 @@ int main(int argc, char *argv[])
if (!error && cur_menu->id == MenuId_SystemTitles) is_system = true; if (!error && cur_menu->id == MenuId_SystemTitles) is_system = true;
} }
if (!error)
{
title_info_count = titleGetCountFromInfoBlock(title_info);
title_info_idx = 1;
}
} else { } else {
if (cur_menu->id == MenuId_SystemTitles) if (cur_menu->id == MenuId_SystemTitles)
{ {
@ -1446,6 +1446,22 @@ static void consolePrint(const char *text, ...)
mutexUnlock(&g_conMutex); mutexUnlock(&g_conMutex);
} }
static void consolePrintReversedColors(const char *text, ...)
{
mutexLock(&g_conMutex);
printf(CONSOLE_ESC(7m));
va_list v;
va_start(v, text);
vfprintf(stdout, text, v);
va_end(v);
printf(CONSOLE_ESC(0m));
mutexUnlock(&g_conMutex);
}
static void consoleRefresh(void) static void consoleRefresh(void)
{ {
mutexLock(&g_conMutex); mutexLock(&g_conMutex);
@ -1616,6 +1632,49 @@ end:
if (app_metadata) free(app_metadata); if (app_metadata) free(app_metadata);
} }
static TitleInfo *getLatestTitleInfo(TitleInfo *title_info, u32 *out_idx, u32 *out_count)
{
if (!title_info || !out_idx || !out_count || (title_info->meta_key.type != NcmContentMetaType_Patch && title_info->meta_key.type != NcmContentMetaType_DataPatch)) return title_info;
u32 idx = 0, count = 1;
TitleInfo *cur_info = title_info->previous, *out = title_info;
while(cur_info)
{
count++;
if (cur_info->version.value > out->version.value)
{
out = cur_info;
idx = count;
}
cur_info = cur_info->previous;
}
idx = (out != title_info ? (count - idx) : (count - 1));
cur_info = title_info->next;
while(cur_info)
{
count++;
if (cur_info->version.value > out->version.value)
{
out = cur_info;
idx = (count - 1);
}
cur_info = cur_info->next;
}
*out_idx = idx;
*out_count = count;
return out;
}
void freeNcaList(void) void freeNcaList(void)
{ {
/* Free all previously allocated data. */ /* Free all previously allocated data. */
@ -1966,7 +2025,7 @@ static bool waitForGameCard(void)
if (status != GameCardStatus_InsertedAndInfoLoaded) if (status != GameCardStatus_InsertedAndInfoLoaded)
{ {
consolePrint("press any button\n"); consolePrint("press any button to go back\n");
utilsWaitForButtonPress(0); utilsWaitForButtonPress(0);
return false; return false;
} }
@ -2502,7 +2561,7 @@ static bool saveGameCardIdSet(void *userdata)
u32 crc = 0; u32 crc = 0;
char *filename = NULL; char *filename = NULL;
if (!gamecardGetIdSet(&id_set)) if (!gamecardGetCardIdSet(&id_set))
{ {
consolePrint("failed to get gamecard id set\n"); consolePrint("failed to get gamecard id set\n");
goto end; goto end;
@ -2987,8 +3046,9 @@ static bool saveNintendoContentArchive(void *userdata)
consolePrint("nca size: 0x%lX\n", shared_thread_data->total_size); consolePrint("nca size: 0x%lX\n", shared_thread_data->total_size);
snprintf(path, MAX_ELEMENTS(path), "/%s.%s", nca_thread_data.nca_ctx->content_id_str, content_info->content_type == NcmContentType_Meta ? "cnmt.nca" : "nca");
snprintf(subdir, MAX_ELEMENTS(subdir), "NCA/%s", nca_thread_data.nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); snprintf(subdir, MAX_ELEMENTS(subdir), "NCA/%s", nca_thread_data.nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User");
snprintf(path, MAX_ELEMENTS(path), "/%s.%s", nca_thread_data.nca_ctx->content_id_str, content_info->content_type == NcmContentType_Meta ? "cnmt.nca" : "nca");
filename = generateOutputTitleFileName(title_info, subdir, path); filename = generateOutputTitleFileName(title_info, subdir, path);
if (!filename) goto end; if (!filename) goto end;
@ -3136,14 +3196,8 @@ static bool saveNintendoContentArchiveFsSection(void *userdata)
} }
/* Initialize base/patch NCA context, if needed. */ /* Initialize base/patch NCA context, if needed. */
if (g_ncaBasePatchTitleInfo) if (base_patch_content_info)
{ {
if (!base_patch_content_info)
{
consolePrint("unable to find content with type %s and id offset %u in selected base/patch title!\n", titleGetNcmContentTypeName(content_type), nca_ctx->id_offset);
goto end;
}
base_patch_nca_ctx = calloc(1, sizeof(NcaContext)); base_patch_nca_ctx = calloc(1, sizeof(NcaContext));
if (!base_patch_nca_ctx) if (!base_patch_nca_ctx)
{ {
@ -3160,6 +3214,12 @@ static bool saveNintendoContentArchiveFsSection(void *userdata)
/* Use a matching NCA FS section entry. */ /* Use a matching NCA FS section entry. */
base_patch_nca_fs_ctx = &(base_patch_nca_ctx->fs_ctx[nca_fs_ctx->section_idx]); base_patch_nca_fs_ctx = &(base_patch_nca_ctx->fs_ctx[nca_fs_ctx->section_idx]);
} else
if (title_type != NcmContentMetaType_Patch)
{
/* Handles edge cases where a specific NCA from an update has no matching equivalent by type/ID offset in its base title (e.g. Fall Guys' HtmlDocument NCA). */
consolePrint("unable to find content with type %s and id offset %u in selected base/patch title!\n", titleGetNcmContentTypeName(content_type), nca_ctx->id_offset);
goto end;
} }
if (section_type == NcaFsSectionType_PartitionFs) if (section_type == NcaFsSectionType_PartitionFs)
@ -3233,8 +3293,7 @@ static bool saveRawPartitionFsSection(PartitionFileSystemContext *pfs_ctx, bool
filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "exefs.nsp"); filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "exefs.nsp");
} else { } else {
snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Raw", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Raw", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User");
snprintf(path, MAX_ELEMENTS(path), "/%s #%u (%s)/Section #%u (%s).nsp", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_ctx->content_id_str, \ snprintf(path, MAX_ELEMENTS(path), "/%s #%u/%u.nsp", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx);
nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx));
TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo);
filename = generateOutputTitleFileName(title_info, subdir, path); filename = generateOutputTitleFileName(title_info, subdir, path);
@ -3389,8 +3448,7 @@ static bool saveRawRomFsSection(RomFileSystemContext *romfs_ctx, bool use_layere
filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "romfs.bin"); filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "romfs.bin");
} else { } else {
snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Raw", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Raw", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User");
snprintf(path, MAX_ELEMENTS(path), "/%s #%u (%s)/Section #%u (%s).bin", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_ctx->content_id_str, \ snprintf(path, MAX_ELEMENTS(path), "/%s #%u/%u.bin", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx);
nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx));
TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo);
filename = generateOutputTitleFileName(title_info, subdir, path); filename = generateOutputTitleFileName(title_info, subdir, path);
@ -4055,8 +4113,7 @@ static void extractedPartitionFsReadThreadFunc(void *arg)
filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "exefs"); filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "exefs");
} else { } else {
snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Extracted", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Extracted", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User");
snprintf(pfs_path, MAX_ELEMENTS(pfs_path), "/%s #%u (%s)/Section #%u (%s)", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_ctx->content_id_str, \ snprintf(pfs_path, MAX_ELEMENTS(pfs_path), "/%s #%u/%u", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx);
nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx));
TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo);
filename = generateOutputTitleFileName(title_info, subdir, pfs_path); filename = generateOutputTitleFileName(title_info, subdir, pfs_path);
@ -4360,8 +4417,7 @@ static void extractedRomFsReadThreadFunc(void *arg)
filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "romfs"); filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "romfs");
} else { } else {
snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Extracted", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Extracted", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User");
snprintf(romfs_path, MAX_ELEMENTS(romfs_path), "/%s #%u (%s)/Section #%u (%s)", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_ctx->content_id_str, \ snprintf(romfs_path, MAX_ELEMENTS(romfs_path), "/%s #%u/%u", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx);
nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx));
TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo);
filename = generateOutputTitleFileName(title_info, subdir, romfs_path); filename = generateOutputTitleFileName(title_info, subdir, romfs_path);
@ -4427,7 +4483,7 @@ static void extractedRomFsReadThreadFunc(void *arg)
/* Retrieve RomFS file entry information and generate output path. */ /* Retrieve RomFS file entry information and generate output path. */
shared_thread_data->read_error = (!(romfs_file_entry = romfsGetCurrentFileEntry(romfs_ctx)) || \ shared_thread_data->read_error = (!(romfs_file_entry = romfsGetCurrentFileEntry(romfs_ctx)) || \
!romfsGeneratePathFromFileEntry(romfs_ctx, romfs_file_entry, romfs_path + filename_len, FS_MAX_PATH - filename_len, romfs_illegal_char_replace_type)); !romfsGeneratePathFromFileEntry(romfs_ctx, romfs_file_entry, romfs_path + filename_len, sizeof(romfs_path) - filename_len, romfs_illegal_char_replace_type));
if (shared_thread_data->read_error) if (shared_thread_data->read_error)
{ {
condvarWakeAll(&g_writeCondvar); condvarWakeAll(&g_writeCondvar);
@ -4758,15 +4814,15 @@ static void nspThreadFunc(void *arg)
u8 *raw_cert_chain = NULL; u8 *raw_cert_chain = NULL;
u64 raw_cert_chain_size = 0; u64 raw_cert_chain_size = 0;
PartitionFileSystemFileContext pfs_file_ctx = {0}; PartitionFileSystemImageContext pfs_img_ctx = {0};
pfsInitializeFileContext(&pfs_file_ctx); pfsInitializeImageContext(&pfs_img_ctx);
char entry_name[64] = {0}; char entry_name[64] = {0};
u64 nsp_header_size = 0, nsp_size = 0, nsp_offset = 0; u64 nsp_header_size = 0, nsp_size = 0, nsp_offset = 0;
char *tmp_name = NULL; char *tmp_name = NULL;
Sha256Context sha256_ctx = {0}; Sha256Context clean_sha256_ctx = {0}, dirty_sha256_ctx = {0};
u8 sha256_hash[SHA256_HASH_SIZE] = {0}; u8 clean_sha256_hash[SHA256_HASH_SIZE] = {0}, dirty_sha256_hash[SHA256_HASH_SIZE] = {0};
if (!nsp_thread_data || !(title_info = (TitleInfo*)nsp_thread_data->data) || !title_info->content_count || !title_info->content_infos) goto end; if (!nsp_thread_data || !(title_info = (TitleInfo*)nsp_thread_data->data) || !title_info->content_count || !title_info->content_infos) goto end;
@ -5023,7 +5079,7 @@ static void nspThreadFunc(void *arg)
NcaContext *cur_nca_ctx = &(nca_ctx[i]); NcaContext *cur_nca_ctx = &(nca_ctx[i]);
sprintf(entry_name, "%s.%s", cur_nca_ctx->content_id_str, cur_nca_ctx->content_type == NcmContentType_Meta ? "cnmt.nca" : "nca"); sprintf(entry_name, "%s.%s", cur_nca_ctx->content_id_str, cur_nca_ctx->content_type == NcmContentType_Meta ? "cnmt.nca" : "nca");
if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_nca_ctx->content_size, NULL)) if (!pfsAddEntryInformationToImageContext(&pfs_img_ctx, entry_name, cur_nca_ctx->content_size, NULL))
{ {
consolePrint("pfs add entry failed: %s\n", entry_name); consolePrint("pfs add entry failed: %s\n", entry_name);
goto end; goto end;
@ -5034,7 +5090,7 @@ static void nspThreadFunc(void *arg)
if (generate_authoringtool_data) if (generate_authoringtool_data)
{ {
sprintf(entry_name, "%s.cnmt.xml", meta_nca_ctx->content_id_str); sprintf(entry_name, "%s.cnmt.xml", meta_nca_ctx->content_id_str);
if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cnmt_ctx.authoring_tool_xml_size, &(meta_nca_ctx->content_type_ctx_data_idx))) if (!pfsAddEntryInformationToImageContext(&pfs_img_ctx, entry_name, cnmt_ctx.authoring_tool_xml_size, &(meta_nca_ctx->content_type_ctx_data_idx)))
{ {
consolePrint("pfs add entry failed: %s\n", entry_name); consolePrint("pfs add entry failed: %s\n", entry_name);
goto end; goto end;
@ -5055,7 +5111,7 @@ static void nspThreadFunc(void *arg)
{ {
ProgramInfoContext *cur_program_info_ctx = (ProgramInfoContext*)cur_nca_ctx->content_type_ctx; ProgramInfoContext *cur_program_info_ctx = (ProgramInfoContext*)cur_nca_ctx->content_type_ctx;
sprintf(entry_name, "%s.programinfo.xml", cur_nca_ctx->content_id_str); sprintf(entry_name, "%s.programinfo.xml", cur_nca_ctx->content_id_str);
ret = pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_program_info_ctx->authoring_tool_xml_size, &(cur_nca_ctx->content_type_ctx_data_idx)); ret = pfsAddEntryInformationToImageContext(&pfs_img_ctx, entry_name, cur_program_info_ctx->authoring_tool_xml_size, &(cur_nca_ctx->content_type_ctx_data_idx));
break; break;
} }
case NcmContentType_Control: case NcmContentType_Control:
@ -5066,7 +5122,7 @@ static void nspThreadFunc(void *arg)
{ {
NacpIconContext *icon_ctx = &(cur_nacp_ctx->icon_ctx[j]); NacpIconContext *icon_ctx = &(cur_nacp_ctx->icon_ctx[j]);
sprintf(entry_name, "%s.nx.%s.jpg", cur_nca_ctx->content_id_str, nacpGetLanguageString(icon_ctx->language)); sprintf(entry_name, "%s.nx.%s.jpg", cur_nca_ctx->content_id_str, nacpGetLanguageString(icon_ctx->language));
if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, icon_ctx->icon_size, j == 0 ? &(cur_nca_ctx->content_type_ctx_data_idx) : NULL)) if (!pfsAddEntryInformationToImageContext(&pfs_img_ctx, entry_name, icon_ctx->icon_size, j == 0 ? &(cur_nca_ctx->content_type_ctx_data_idx) : NULL))
{ {
consolePrint("pfs add entry failed: %s\n", entry_name); consolePrint("pfs add entry failed: %s\n", entry_name);
goto end; goto end;
@ -5074,14 +5130,14 @@ static void nspThreadFunc(void *arg)
} }
sprintf(entry_name, "%s.nacp.xml", cur_nca_ctx->content_id_str); sprintf(entry_name, "%s.nacp.xml", cur_nca_ctx->content_id_str);
ret = pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_nacp_ctx->authoring_tool_xml_size, !cur_nacp_ctx->icon_count ? &(cur_nca_ctx->content_type_ctx_data_idx) : NULL); ret = pfsAddEntryInformationToImageContext(&pfs_img_ctx, entry_name, cur_nacp_ctx->authoring_tool_xml_size, !cur_nacp_ctx->icon_count ? &(cur_nca_ctx->content_type_ctx_data_idx) : NULL);
break; break;
} }
case NcmContentType_LegalInformation: case NcmContentType_LegalInformation:
{ {
LegalInfoContext *cur_legal_info_ctx = (LegalInfoContext*)cur_nca_ctx->content_type_ctx; LegalInfoContext *cur_legal_info_ctx = (LegalInfoContext*)cur_nca_ctx->content_type_ctx;
sprintf(entry_name, "%s.legalinfo.xml", cur_nca_ctx->content_id_str); sprintf(entry_name, "%s.legalinfo.xml", cur_nca_ctx->content_id_str);
ret = pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_legal_info_ctx->authoring_tool_xml_size, &(cur_nca_ctx->content_type_ctx_data_idx)); ret = pfsAddEntryInformationToImageContext(&pfs_img_ctx, entry_name, cur_legal_info_ctx->authoring_tool_xml_size, &(cur_nca_ctx->content_type_ctx_data_idx));
break; break;
} }
default: default:
@ -5099,28 +5155,28 @@ static void nspThreadFunc(void *arg)
if (retrieve_tik_cert) if (retrieve_tik_cert)
{ {
sprintf(entry_name, "%s.tik", tik.rights_id_str); sprintf(entry_name, "%s.tik", tik.rights_id_str);
if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, tik.size, NULL)) if (!pfsAddEntryInformationToImageContext(&pfs_img_ctx, entry_name, tik.size, NULL))
{ {
consolePrint("pfs add entry failed: %s\n", entry_name); consolePrint("pfs add entry failed: %s\n", entry_name);
goto end; goto end;
} }
sprintf(entry_name, "%s.cert", tik.rights_id_str); sprintf(entry_name, "%s.cert", tik.rights_id_str);
if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, raw_cert_chain_size, NULL)) if (!pfsAddEntryInformationToImageContext(&pfs_img_ctx, entry_name, raw_cert_chain_size, NULL))
{ {
consolePrint("pfs add entry failed: %s\n", entry_name); consolePrint("pfs add entry failed: %s\n", entry_name);
goto end; goto end;
} }
} }
// write buffer to memory buffer // write pfs header to memory buffer
if (!pfsWriteFileContextHeaderToMemoryBuffer(&pfs_file_ctx, buf, BLOCK_SIZE, &nsp_header_size)) if (!pfsWriteImageContextHeaderToMemoryBuffer(&pfs_img_ctx, buf, BLOCK_SIZE, &nsp_header_size))
{ {
consolePrint("pfs write header to mem #1 failed\n"); consolePrint("pfs write header to mem #1 failed\n");
goto end; goto end;
} }
nsp_size = (nsp_header_size + pfs_file_ctx.fs_size); nsp_size = (nsp_header_size + pfs_img_ctx.fs_size);
consolePrint("nsp header size: 0x%lX | nsp size: 0x%lX\n", nsp_header_size, nsp_size); consolePrint("nsp header size: 0x%lX | nsp size: 0x%lX\n", nsp_header_size, nsp_size);
consoleRefresh(); consoleRefresh();
@ -5183,8 +5239,8 @@ static void nspThreadFunc(void *arg)
NcaContext *cur_nca_ctx = &(nca_ctx[i]); NcaContext *cur_nca_ctx = &(nca_ctx[i]);
u64 blksize = BLOCK_SIZE; u64 blksize = BLOCK_SIZE;
memset(&sha256_ctx, 0, sizeof(Sha256Context)); sha256ContextCreate(&clean_sha256_ctx);
sha256ContextCreate(&sha256_ctx); sha256ContextCreate(&dirty_sha256_ctx);
if (cur_nca_ctx->content_type == NcmContentType_Meta && (!cnmtGenerateNcaPatch(&cnmt_ctx) || !ncaEncryptHeader(cur_nca_ctx))) if (cur_nca_ctx->content_type == NcmContentType_Meta && (!cnmtGenerateNcaPatch(&cnmt_ctx) || !ncaEncryptHeader(cur_nca_ctx)))
{ {
@ -5196,7 +5252,7 @@ static void nspThreadFunc(void *arg)
if (dev_idx == 1) if (dev_idx == 1)
{ {
tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, i); tmp_name = pfsGetEntryNameByIndexFromImageContext(&pfs_img_ctx, i);
if (!usbSendFileProperties(cur_nca_ctx->content_size, tmp_name)) if (!usbSendFileProperties(cur_nca_ctx->content_size, tmp_name))
{ {
consolePrint("usb send file properties \"%s\" failed\n", tmp_name); consolePrint("usb send file properties \"%s\" failed\n", tmp_name);
@ -5225,6 +5281,9 @@ static void nspThreadFunc(void *arg)
goto end; goto end;
} }
// update clean hash calculation
sha256ContextUpdate(&clean_sha256_ctx, buf, blksize);
if (dirty_header) if (dirty_header)
{ {
// write re-encrypted headers // write re-encrypted headers
@ -5250,8 +5309,8 @@ static void nspThreadFunc(void *arg)
dirty_header = (!cur_nca_ctx->header_written || cur_nca_ctx->content_type_ctx_patch); dirty_header = (!cur_nca_ctx->header_written || cur_nca_ctx->content_type_ctx_patch);
} }
// update hash calculation // update dirty hash calculation
sha256ContextUpdate(&sha256_ctx, buf, blksize); sha256ContextUpdate(&dirty_sha256_ctx, buf, blksize);
// write nca chunk // write nca chunk
if (dev_idx == 1) if (dev_idx == 1)
@ -5266,24 +5325,35 @@ static void nspThreadFunc(void *arg)
} }
} }
// get hash // get hashes
sha256ContextGetHash(&sha256_ctx, sha256_hash); sha256ContextGetHash(&clean_sha256_ctx, clean_sha256_hash);
sha256ContextGetHash(&dirty_sha256_ctx, dirty_sha256_hash);
// update content id and hash // verify content hash
ncaUpdateContentIdAndHash(cur_nca_ctx, sha256_hash); if (!cnmtVerifyContentHash(&cnmt_ctx, cur_nca_ctx, clean_sha256_hash))
// update cnmt
if (!cnmtUpdateContentInfo(&cnmt_ctx, cur_nca_ctx))
{ {
consolePrint("cnmt update content info failed\n"); consolePrint("sha256 checksum mismatch for nca \"%s\"\n", cur_nca_ctx->content_id_str);
goto end; goto end;
} }
// update pfs entry name if (memcmp(clean_sha256_hash, dirty_sha256_hash, SHA256_HASH_SIZE) != 0)
if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, i, cur_nca_ctx->content_id_str))
{ {
consolePrint("pfs update entry name failed for nca \"%s\"\n", cur_nca_ctx->content_id_str); // update content id and hash
goto end; ncaUpdateContentIdAndHash(cur_nca_ctx, dirty_sha256_hash);
// update cnmt
if (!cnmtUpdateContentInfo(&cnmt_ctx, cur_nca_ctx))
{
consolePrint("cnmt update content info failed\n");
goto end;
}
// update pfs entry name
if (!pfsUpdateEntryNameFromImageContext(&pfs_img_ctx, i, cur_nca_ctx->content_id_str))
{
consolePrint("pfs update entry name failed for nca \"%s\"\n", cur_nca_ctx->content_id_str);
goto end;
}
} }
} }
@ -5299,7 +5369,7 @@ static void nspThreadFunc(void *arg)
// write cnmt xml // write cnmt xml
if (dev_idx == 1) if (dev_idx == 1)
{ {
tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, meta_nca_ctx->content_type_ctx_data_idx); tmp_name = pfsGetEntryNameByIndexFromImageContext(&pfs_img_ctx, meta_nca_ctx->content_type_ctx_data_idx);
if (!usbSendFileProperties(cnmt_ctx.authoring_tool_xml_size, tmp_name) || !usbSendFileData(cnmt_ctx.authoring_tool_xml, cnmt_ctx.authoring_tool_xml_size)) if (!usbSendFileProperties(cnmt_ctx.authoring_tool_xml_size, tmp_name) || !usbSendFileData(cnmt_ctx.authoring_tool_xml, cnmt_ctx.authoring_tool_xml_size))
{ {
consolePrint("send \"%s\" failed\n", tmp_name); consolePrint("send \"%s\" failed\n", tmp_name);
@ -5313,7 +5383,7 @@ static void nspThreadFunc(void *arg)
nsp_thread_data->data_written += cnmt_ctx.authoring_tool_xml_size; nsp_thread_data->data_written += cnmt_ctx.authoring_tool_xml_size;
// update cnmt xml pfs entry name // update cnmt xml pfs entry name
if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, meta_nca_ctx->content_type_ctx_data_idx, meta_nca_ctx->content_id_str)) if (!pfsUpdateEntryNameFromImageContext(&pfs_img_ctx, meta_nca_ctx->content_type_ctx_data_idx, meta_nca_ctx->content_id_str))
{ {
consolePrint("pfs update entry name cnmt xml failed\n"); consolePrint("pfs update entry name cnmt xml failed\n");
goto end; goto end;
@ -5353,7 +5423,7 @@ static void nspThreadFunc(void *arg)
// write icon // write icon
if (dev_idx == 1) if (dev_idx == 1)
{ {
tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, data_idx); tmp_name = pfsGetEntryNameByIndexFromImageContext(&pfs_img_ctx, data_idx);
if (!usbSendFileProperties(icon_ctx->icon_size, tmp_name) || !usbSendFileData(icon_ctx->icon_data, icon_ctx->icon_size)) if (!usbSendFileProperties(icon_ctx->icon_size, tmp_name) || !usbSendFileData(icon_ctx->icon_data, icon_ctx->icon_size))
{ {
consolePrint("send \"%s\" failed\n", tmp_name); consolePrint("send \"%s\" failed\n", tmp_name);
@ -5367,7 +5437,7 @@ static void nspThreadFunc(void *arg)
nsp_thread_data->data_written += icon_ctx->icon_size; nsp_thread_data->data_written += icon_ctx->icon_size;
// update pfs entry name // update pfs entry name
if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, data_idx++, cur_nca_ctx->content_id_str)) if (!pfsUpdateEntryNameFromImageContext(&pfs_img_ctx, data_idx++, cur_nca_ctx->content_id_str))
{ {
consolePrint("pfs update entry name failed for icon \"%s\" (%u)\n", cur_nca_ctx->content_id_str, icon_ctx->language); consolePrint("pfs update entry name failed for icon \"%s\" (%u)\n", cur_nca_ctx->content_id_str, icon_ctx->language);
goto end; goto end;
@ -5390,7 +5460,7 @@ static void nspThreadFunc(void *arg)
// write xml // write xml
if (dev_idx == 1) if (dev_idx == 1)
{ {
tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, data_idx); tmp_name = pfsGetEntryNameByIndexFromImageContext(&pfs_img_ctx, data_idx);
if (!usbSendFileProperties(authoring_tool_xml_size, tmp_name) || !usbSendFileData(authoring_tool_xml, authoring_tool_xml_size)) if (!usbSendFileProperties(authoring_tool_xml_size, tmp_name) || !usbSendFileData(authoring_tool_xml, authoring_tool_xml_size))
{ {
consolePrint("send \"%s\" failed\n", tmp_name); consolePrint("send \"%s\" failed\n", tmp_name);
@ -5404,7 +5474,7 @@ static void nspThreadFunc(void *arg)
nsp_thread_data->data_written += authoring_tool_xml_size; nsp_thread_data->data_written += authoring_tool_xml_size;
// update pfs entry name // update pfs entry name
if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, data_idx, cur_nca_ctx->content_id_str)) if (!pfsUpdateEntryNameFromImageContext(&pfs_img_ctx, data_idx, cur_nca_ctx->content_id_str))
{ {
consolePrint("pfs update entry name failed for xml \"%s\"\n", cur_nca_ctx->content_id_str); consolePrint("pfs update entry name failed for xml \"%s\"\n", cur_nca_ctx->content_id_str);
goto end; goto end;
@ -5416,7 +5486,7 @@ static void nspThreadFunc(void *arg)
// write ticket // write ticket
if (dev_idx == 1) if (dev_idx == 1)
{ {
tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, pfs_file_ctx.header.entry_count - 2); tmp_name = pfsGetEntryNameByIndexFromImageContext(&pfs_img_ctx, pfs_img_ctx.header.entry_count - 2);
if (!usbSendFileProperties(tik.size, tmp_name) || !usbSendFileData(tik.data, tik.size)) if (!usbSendFileProperties(tik.size, tmp_name) || !usbSendFileData(tik.data, tik.size))
{ {
consolePrint("send \"%s\" failed\n", tmp_name); consolePrint("send \"%s\" failed\n", tmp_name);
@ -5432,7 +5502,7 @@ static void nspThreadFunc(void *arg)
// write cert // write cert
if (dev_idx == 1) if (dev_idx == 1)
{ {
tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, pfs_file_ctx.header.entry_count - 1); tmp_name = pfsGetEntryNameByIndexFromImageContext(&pfs_img_ctx, pfs_img_ctx.header.entry_count - 1);
if (!usbSendFileProperties(raw_cert_chain_size, tmp_name) || !usbSendFileData(raw_cert_chain, raw_cert_chain_size)) if (!usbSendFileProperties(raw_cert_chain_size, tmp_name) || !usbSendFileData(raw_cert_chain, raw_cert_chain_size))
{ {
consolePrint("send \"%s\" failed\n", tmp_name); consolePrint("send \"%s\" failed\n", tmp_name);
@ -5447,7 +5517,7 @@ static void nspThreadFunc(void *arg)
} }
// write new pfs0 header // write new pfs0 header
if (!pfsWriteFileContextHeaderToMemoryBuffer(&pfs_file_ctx, buf, BLOCK_SIZE, &nsp_header_size)) if (!pfsWriteImageContextHeaderToMemoryBuffer(&pfs_img_ctx, buf, BLOCK_SIZE, &nsp_header_size))
{ {
consolePrint("pfs write header to mem #2 failed\n"); consolePrint("pfs write header to mem #2 failed\n");
goto end; goto end;
@ -5492,7 +5562,7 @@ end:
} }
} }
pfsFreeFileContext(&pfs_file_ctx); pfsFreeImageContext(&pfs_img_ctx);
if (raw_cert_chain) free(raw_cert_chain); if (raw_cert_chain) free(raw_cert_chain);

View file

@ -318,6 +318,9 @@ typedef struct {
/// Initializes a ContentMetaContext using a previously initialized NcaContext (which must belong to a Meta NCA). /// Initializes a ContentMetaContext using a previously initialized NcaContext (which must belong to a Meta NCA).
bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx); bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx);
/// Looks for a NcmPackagedContentInfo entry with a content ID that matches the one from the input NcaContext and verifies its hash.
bool cnmtVerifyContentHash(ContentMetaContext *cnmt_ctx, NcaContext *nca_ctx, const u8 *hash);
/// Updates NcmPackagedContentInfo data for the content entry with size, type and ID offset values that match the ones from the input NcaContext. /// Updates NcmPackagedContentInfo data for the content entry with size, type and ID offset values that match the ones from the input NcaContext.
bool cnmtUpdateContentInfo(ContentMetaContext *cnmt_ctx, NcaContext *nca_ctx); bool cnmtUpdateContentInfo(ContentMetaContext *cnmt_ctx, NcaContext *nca_ctx);

View file

@ -45,19 +45,57 @@ typedef struct {
NXDT_ASSERT(FsGameCardCertificate, 0x200); NXDT_ASSERT(FsGameCardCertificate, 0x200);
typedef enum {
FsCardId1MakerCode_MegaChips = 0xC2,
FsCardId1MakerCode_Lapis = 0xAE,
FsCardId1MakerCode_Unknown = 0x36 ///< Seen in TLoZ:TotK, SMBW and other modern releases.
} FsCardId1MakerCode;
typedef enum {
FsCardId1MemoryType_None = 0,
FsCardId1MemoryType_CardModeT1 = BIT(0),
FsCardId1MemoryType_CardModeT2 = BIT(1),
FsCardId1MemoryType_Unknown1 = BIT(2), ///< Related to CardMode?
FsCardId1MemoryType_IsNand = BIT(3), ///< 0: Rom, 1: Nand.
FsCardId1MemoryType_Unknown2 = BIT(4), ///< Related to Nand memory type?
FsCardId1MemoryType_IsLate = BIT(5), ///< 0: Fast, 1: Late.
FsCardId1MemoryType_Unknown3 = BIT(6),
FsCardId1MemoryType_Unknown4 = BIT(7),
FsCardId1MemoryType_Count = 8, ///< Total values supported by this enum.
///< Values defined in Atmosphère source code.
FsCardId1MemoryType_T1RomFast = FsCardId1MemoryType_CardModeT1,
FsCardId1MemoryType_T2RomFast = FsCardId1MemoryType_CardModeT2,
FsCardId1MemoryType_T1NandFast = (FsCardId1MemoryType_IsNand | FsCardId1MemoryType_CardModeT1),
FsCardId1MemoryType_T2NandFast = (FsCardId1MemoryType_IsNand | FsCardId1MemoryType_CardModeT2),
FsCardId1MemoryType_T1RomLate = (FsCardId1MemoryType_IsLate | FsCardId1MemoryType_CardModeT1),
FsCardId1MemoryType_T2RomLate = (FsCardId1MemoryType_IsLate | FsCardId1MemoryType_CardModeT2),
FsCardId1MemoryType_T1NandLate = (FsCardId1MemoryType_IsLate | FsCardId1MemoryType_IsNand | FsCardId1MemoryType_CardModeT1),
FsCardId1MemoryType_T2NandLate = (FsCardId1MemoryType_IsLate | FsCardId1MemoryType_IsNand | FsCardId1MemoryType_CardModeT2)
} FsCardId1MemoryType;
typedef struct { typedef struct {
u8 maker_code; ///< Usually 0xC2 (Macronix). u8 maker_code; ///< FsCardId1MakerCode.
u8 memory_capacity; ///< Matches GameCardRomSize. u8 memory_capacity; ///< Matches GameCardRomSize.
u8 reserved; ///< Known values: 0x06, 0x09, 0x0A. u8 reserved; ///< Known values: 0x00, 0x01, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x80.
u8 memory_type; ///< Usually 0x21. u8 memory_type; ///< FsCardId1MemoryType.
} FsCardId1; } FsCardId1;
NXDT_ASSERT(FsCardId1, 0x4); NXDT_ASSERT(FsCardId1, 0x4);
typedef enum {
FsCardId2CardType_Rom = 0,
FsCardId2CardType_WritableDevT1 = 1,
FsCardId2CardType_WritableProdT1 = 2,
FsCardId2CardType_WritableDevT2 = 3,
FsCardId2CardType_WritableProdT2 = 4,
FsCardId2CardType_Count = 5 ///< Total values supported by this enum.
} FsCardId2CardType;
typedef struct { typedef struct {
u8 card_security_number; ///< Usually 0x02. u8 sel_t1_key; ///< Matches sel_t1_key value from GameCardHeader (usually 0x02).
u8 card_type; ///< Usually 0x00. u8 card_type; ///< FsCardId2CardType.
u8 reserved[0x2]; ///< Usually filled with zeroes. u8 reserved[0x2]; ///< Usually filled with zeroes.
} FsCardId2; } FsCardId2;
NXDT_ASSERT(FsCardId2, 0x4); NXDT_ASSERT(FsCardId2, 0x4);
@ -84,7 +122,6 @@ Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier *out);
/// IDeviceOperator. /// IDeviceOperator.
Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator *d, const FsGameCardHandle *handle, u32 *out_title_version, u64 *out_title_id); Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator *d, const FsGameCardHandle *handle, u32 *out_title_version, u64 *out_title_id);
Result fsDeviceOperatorGetGameCardDeviceCertificate(FsDeviceOperator *d, const FsGameCardHandle *handle, FsGameCardCertificate *out); Result fsDeviceOperatorGetGameCardDeviceCertificate(FsDeviceOperator *d, const FsGameCardHandle *handle, FsGameCardCertificate *out);
Result fsDeviceOperatorGetGameCardIdSet(FsDeviceOperator *d, FsGameCardIdSet *out);
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -45,7 +45,7 @@ typedef struct {
union { union {
u8 value[0x10]; u8 value[0x10];
struct { struct {
u64 package_id; ///< Matches package_id from GameCardHeader. u8 package_id[0x8]; ///< Matches package_id from GameCardHeader.
u8 reserved[0x8]; ///< Just zeroes. u8 reserved[0x8]; ///< Just zeroes.
}; };
}; };
@ -200,14 +200,14 @@ NXDT_ASSERT(GameCardInfo, 0x70);
typedef struct { typedef struct {
u8 signature[0x100]; ///< RSA-2048-PSS with SHA-256 signature over the rest of the header. u8 signature[0x100]; ///< RSA-2048-PSS with SHA-256 signature over the rest of the header.
u32 magic; ///< "HEAD". u32 magic; ///< "HEAD".
u32 rom_area_start_page_address; ///< Expressed in GAMECARD_PAGE_SIZE units. u32 rom_area_start_page; ///< Expressed in GAMECARD_PAGE_SIZE units.
u32 backup_area_start_page_address; ///< Always 0xFFFFFFFF. u32 backup_area_start_page; ///< Always 0xFFFFFFFF.
GameCardKeyIndex key_index; GameCardKeyIndex key_index;
u8 rom_size; ///< GameCardRomSize. u8 rom_size; ///< GameCardRomSize.
u8 header_version; ///< Always 0. u8 version; ///< Always 0x00.
u8 flags; ///< GameCardFlags. u8 flags; ///< GameCardFlags.
u64 package_id; ///< Used for challenge-response authentication. u8 package_id[0x8]; ///< Used for challenge-response authentication.
u32 valid_data_end_address; ///< Expressed in GAMECARD_PAGE_SIZE units. u32 valid_data_end_page; ///< Expressed in GAMECARD_PAGE_SIZE units.
u8 reserved[0x4]; u8 reserved[0x4];
u8 card_info_iv[AES_128_KEY_SIZE]; ///< AES-128-CBC IV for the CardInfo area (reversed). u8 card_info_iv[AES_128_KEY_SIZE]; ///< AES-128-CBC IV for the CardInfo area (reversed).
u64 partition_fs_header_address; ///< Root Hash File System header offset. u64 partition_fs_header_address; ///< Root Hash File System header offset.
@ -215,9 +215,9 @@ typedef struct {
u8 partition_fs_header_hash[SHA256_HASH_SIZE]; u8 partition_fs_header_hash[SHA256_HASH_SIZE];
u8 initial_data_hash[SHA256_HASH_SIZE]; u8 initial_data_hash[SHA256_HASH_SIZE];
u32 sel_sec; ///< GameCardSelSec. u32 sel_sec; ///< GameCardSelSec.
u32 sel_t1_key; ///< Always 2. u32 sel_t1_key; ///< Always 0x02.
u32 sel_key; ///< Always 0. u32 sel_key; ///< Always 0x00.
u32 lim_area; ///< Expressed in GAMECARD_PAGE_SIZE units. u32 lim_area_page; ///< Expressed in GAMECARD_PAGE_SIZE units.
GameCardInfo card_info; GameCardInfo card_info;
} GameCardHeader; } GameCardHeader;
@ -291,7 +291,7 @@ bool gamecardGetSecurityInformation(GameCardSecurityInformation *out);
/// Fills the provided FsGameCardIdSet pointer. /// Fills the provided FsGameCardIdSet pointer.
/// This area can't be read using gamecardReadStorage(). /// This area can't be read using gamecardReadStorage().
bool gamecardGetIdSet(FsGameCardIdSet *out); bool gamecardGetCardIdSet(FsGameCardIdSet *out);
/// Fills the provided pointers with LAFW blob data from FS program memory. /// Fills the provided pointers with LAFW blob data from FS program memory.
/// 'out_lafw_blob' or 'out_lafw_version' may be set to NULL, but at least one of them must be a valid pointer. /// 'out_lafw_blob' or 'out_lafw_version' may be set to NULL, but at least one of them must be a valid pointer.

View file

@ -444,7 +444,7 @@ struct _NcaContext {
NcmContentId content_id; ///< Content ID for this NCA. Used to read NCA data from eMMC/SD. Retrieved from NcmContentInfo. NcmContentId content_id; ///< Content ID for this NCA. Used to read NCA data from eMMC/SD. Retrieved from NcmContentInfo.
char content_id_str[0x21]; char content_id_str[0x21];
u8 hash[SHA256_HASH_SIZE]; ///< Manually calculated (if needed). u8 hash[SHA256_HASH_SIZE]; ///< Manually calculated (if needed).
char hash_str[0x41]; char hash_str[SHA256_HASH_STR_SIZE];
u8 format_version; ///< NcaVersion. u8 format_version; ///< NcaVersion.
u8 content_type; ///< NcmContentType. Retrieved from NcmContentInfo. u8 content_type; ///< NcmContentType. Retrieved from NcmContentInfo.
u64 content_size; ///< Retrieved from NcmContentInfo. u64 content_size; ///< Retrieved from NcmContentInfo.
@ -561,7 +561,7 @@ bool ncaEncryptHeader(NcaContext *ctx);
void ncaWriteEncryptedHeaderDataToMemoryBuffer(NcaContext *ctx, void *buf, u64 buf_size, u64 buf_offset); void ncaWriteEncryptedHeaderDataToMemoryBuffer(NcaContext *ctx, void *buf, u64 buf_size, u64 buf_offset);
/// Updates the content ID and hash from a NCA context using a provided SHA-256 checksum. /// Updates the content ID and hash from a NCA context using a provided SHA-256 checksum.
void ncaUpdateContentIdAndHash(NcaContext *ctx, u8 hash[SHA256_HASH_SIZE]); void ncaUpdateContentIdAndHash(NcaContext *ctx, const u8 *hash);
/// Returns a pointer to a string holding the name of the section type from the provided NCA FS section context. /// Returns a pointer to a string holding the name of the section type from the provided NCA FS section context.
const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx); const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx);

View file

@ -61,13 +61,13 @@ typedef struct {
u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table. u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table.
} PartitionFileSystemContext; } PartitionFileSystemContext;
/// Used with Partition FS images (e.g. NSPs). /// Used to generate Partition FS images (e.g. NSPs).
typedef struct { typedef struct {
PartitionFileSystemHeader header; ///< Partition FS header. Holds the entry count and name table size. PartitionFileSystemHeader header; ///< Partition FS header. Holds the entry count and name table size.
PartitionFileSystemEntry *entries; ///< Partition FS entries. PartitionFileSystemEntry *entries; ///< Partition FS entries.
char *name_table; ///< Name table. char *name_table; ///< Name table.
u64 fs_size; ///< Partition FS data size. Updated each time a new entry is added. u64 fs_size; ///< Partition FS data size. Updated each time a new entry is added.
} PartitionFileSystemFileContext; } PartitionFileSystemImageContext;
/// Initializes a Partition FS context. /// Initializes a Partition FS context.
bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx); bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx);
@ -92,15 +92,15 @@ bool pfsGetTotalDataSize(PartitionFileSystemContext *ctx, u64 *out_size);
/// Use the pfsWriteEntryPatchToMemoryBuffer() wrapper to write patch data generated by this function. /// Use the pfsWriteEntryPatchToMemoryBuffer() wrapper to write patch data generated by this function.
bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out); bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out);
/// Adds a new Partition FS entry to an existing PartitionFileSystemFileContext, using the provided entry name and size. /// Adds a new Partition FS entry to an existing PartitionFileSystemImageContext, using the provided entry name and size.
/// If 'out_entry_idx' is a valid pointer, the index to the new Partition FS entry will be saved to it. /// If 'out_entry_idx' is a valid pointer, the index to the new Partition FS entry will be saved to it.
bool pfsAddEntryInformationToFileContext(PartitionFileSystemFileContext *ctx, const char *entry_name, u64 entry_size, u32 *out_entry_idx); bool pfsAddEntryInformationToImageContext(PartitionFileSystemImageContext *ctx, const char *entry_name, u64 entry_size, u32 *out_entry_idx);
/// Updates the name from a Partition FS entry in an existing PartitionFileSystemFileContext, using an entry index and the new entry name. /// Updates the name from a Partition FS entry in an existing PartitionFileSystemImageContext, using an entry index and the new entry name.
bool pfsUpdateEntryNameFromFileContext(PartitionFileSystemFileContext *ctx, u32 entry_idx, const char *new_entry_name); bool pfsUpdateEntryNameFromImageContext(PartitionFileSystemImageContext *ctx, u32 entry_idx, const char *new_entry_name);
/// Generates a full Partition FS header from an existing PartitionFileSystemFileContext and writes it to the provided memory buffer. /// Generates a full Partition FS header from an existing PartitionFileSystemImageContext and writes it to the provided memory buffer.
bool pfsWriteFileContextHeaderToMemoryBuffer(PartitionFileSystemFileContext *ctx, void *buf, u64 buf_size, u64 *out_header_size); bool pfsWriteImageContextHeaderToMemoryBuffer(PartitionFileSystemImageContext *ctx, void *buf, u64 buf_size, u64 *out_header_size);
/// Miscellaneous functions. /// Miscellaneous functions.
@ -165,35 +165,35 @@ NX_INLINE void pfsFreeEntryPatch(NcaHierarchicalSha256Patch *patch)
ncaFreeHierarchicalSha256Patch(patch); ncaFreeHierarchicalSha256Patch(patch);
} }
NX_INLINE void pfsFreeFileContext(PartitionFileSystemFileContext *ctx) NX_INLINE void pfsFreeImageContext(PartitionFileSystemImageContext *ctx)
{ {
if (!ctx) return; if (!ctx) return;
if (ctx->entries) free(ctx->entries); if (ctx->entries) free(ctx->entries);
if (ctx->name_table) free(ctx->name_table); if (ctx->name_table) free(ctx->name_table);
memset(ctx, 0, sizeof(PartitionFileSystemFileContext)); memset(ctx, 0, sizeof(PartitionFileSystemImageContext));
} }
NX_INLINE void pfsInitializeFileContext(PartitionFileSystemFileContext *ctx) NX_INLINE void pfsInitializeImageContext(PartitionFileSystemImageContext *ctx)
{ {
if (!ctx) return; if (!ctx) return;
pfsFreeFileContext(ctx); pfsFreeImageContext(ctx);
ctx->header.magic = __builtin_bswap32(PFS0_MAGIC); ctx->header.magic = __builtin_bswap32(PFS0_MAGIC);
} }
NX_INLINE u32 pfsGetEntryCountFromFileContext(PartitionFileSystemFileContext *ctx) NX_INLINE u32 pfsGetEntryCountFromImageContext(PartitionFileSystemImageContext *ctx)
{ {
return (ctx ? ctx->header.entry_count : 0); return (ctx ? ctx->header.entry_count : 0);
} }
NX_INLINE PartitionFileSystemEntry *pfsGetEntryByIndexFromFileContext(PartitionFileSystemFileContext *ctx, u32 idx) NX_INLINE PartitionFileSystemEntry *pfsGetEntryByIndexFromImageContext(PartitionFileSystemImageContext *ctx, u32 idx)
{ {
if (idx >= pfsGetEntryCountFromFileContext(ctx)) return NULL; if (idx >= pfsGetEntryCountFromImageContext(ctx)) return NULL;
return &(ctx->entries[idx]); return &(ctx->entries[idx]);
} }
NX_INLINE char *pfsGetEntryNameByIndexFromFileContext(PartitionFileSystemFileContext *ctx, u32 idx) NX_INLINE char *pfsGetEntryNameByIndexFromImageContext(PartitionFileSystemImageContext *ctx, u32 idx)
{ {
PartitionFileSystemEntry *fs_entry = pfsGetEntryByIndexFromFileContext(ctx, idx); PartitionFileSystemEntry *fs_entry = pfsGetEntryByIndexFromImageContext(ctx, idx);
if (!fs_entry || !ctx->name_table) return NULL; if (!fs_entry || !ctx->name_table) return NULL;
return (ctx->name_table + fs_entry->name_offset); return (ctx->name_table + fs_entry->name_offset);
} }

View file

@ -139,7 +139,7 @@ typedef enum {
} RomFileSystemPathIllegalCharReplaceType; } RomFileSystemPathIllegalCharReplaceType;
/// Initializes a RomFS or Patch RomFS context. /// Initializes a RomFS or Patch RomFS context.
/// 'base_nca_fs_ctx' must always be provided. /// 'base_nca_fs_ctx' shall be NULL *only* if a NCA from an update has no matching equivalent available in its base title.
/// 'patch_nca_fs_ctx' shall be NULL if not dealing with a Patch RomFS. /// 'patch_nca_fs_ctx' shall be NULL if not dealing with a Patch RomFS.
bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base_nca_fs_ctx, NcaFsSectionContext *patch_nca_fs_ctx); bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base_nca_fs_ctx, NcaFsSectionContext *patch_nca_fs_ctx);

View file

@ -60,7 +60,9 @@
/* Global constants used throughout the application. */ /* Global constants used throughout the application. */
#define THIRTY_FPS_DELAY (u64)33333333 /* 1 / 30 = 33.33 milliseconds. */ #define SHA256_HASH_STR_SIZE ((SHA256_HASH_SIZE * 2) + 1) /* Includes NULL terminator. */
#define THIRTY_FPS_DELAY (u64)33333333 /* 1 / 30 = 33.33 milliseconds. */
#define FS_SYSMODULE_TID (u64)0x0100000000000000 #define FS_SYSMODULE_TID (u64)0x0100000000000000
#define BOOT_SYSMODULE_TID (u64)0x0100000000000005 #define BOOT_SYSMODULE_TID (u64)0x0100000000000005
@ -69,7 +71,7 @@
#define SYSTEM_UPDATE_TID (u64)0x0100000000000816 #define SYSTEM_UPDATE_TID (u64)0x0100000000000816
#define QLAUNCH_TID (u64)0x0100000000001000 #define QLAUNCH_TID (u64)0x0100000000001000
#define FAT32_FILESIZE_LIMIT (u64)0xFFFFFFFF /* 4 GiB - 1 (4294967295 bytes). */ #define FAT32_FILESIZE_LIMIT (u64)0xFFFFFFFF /* 4 GiB - 1 (4294967295 bytes). */
#define UTF8_BOM "\xEF\xBB\xBF" #define UTF8_BOM "\xEF\xBB\xBF"
#define CRLF "\r\n" #define CRLF "\r\n"
@ -93,22 +95,22 @@
#define NRO_PATH DEVOPTAB_SDMC_DEVICE APP_BASE_PATH NRO_NAME #define NRO_PATH DEVOPTAB_SDMC_DEVICE APP_BASE_PATH NRO_NAME
#define NRO_TMP_PATH NRO_PATH ".tmp" #define NRO_TMP_PATH NRO_PATH ".tmp"
#define PROD_KEYS_FILE_PATH DEVOPTAB_SDMC_DEVICE HBMENU_BASE_PATH "prod.keys" /* Location used by Lockpick_RCM for retail unit keys. */ #define PROD_KEYS_FILE_PATH DEVOPTAB_SDMC_DEVICE HBMENU_BASE_PATH "prod.keys" /* Location used by Lockpick_RCM for retail unit keys. */
#define DEV_KEYS_FILE_PATH DEVOPTAB_SDMC_DEVICE HBMENU_BASE_PATH "dev.keys" /* Location used by Lockpick_RCM for development unit keys. */ #define DEV_KEYS_FILE_PATH DEVOPTAB_SDMC_DEVICE HBMENU_BASE_PATH "dev.keys" /* Location used by Lockpick_RCM for development unit keys. */
#define LOG_FILE_NAME APP_TITLE ".log" #define LOG_FILE_NAME APP_TITLE ".log"
#define LOG_BUF_SIZE 0x400000 /* 4 MiB. */ #define LOG_BUF_SIZE 0x400000 /* 4 MiB. */
#define LOG_FORCE_FLUSH 0 /* Forces a log buffer flush each time the logfile is written to. */ #define LOG_FORCE_FLUSH 0 /* Forces a log buffer flush each time the logfile is written to. */
#define BIS_SYSTEM_PARTITION_MOUNT_NAME "sys:" #define BIS_SYSTEM_PARTITION_MOUNT_NAME "sys:"
#define DOWNLOAD_TASK_INTERVAL 100 /* 100 milliseconds. */ #define DOWNLOAD_TASK_INTERVAL 100 /* 100 milliseconds. */
#define HTTP_USER_AGENT APP_TITLE "/" APP_VERSION " (Nintendo Switch)" #define HTTP_USER_AGENT APP_TITLE "/" APP_VERSION " (Nintendo Switch)"
#define HTTP_CONNECT_TIMEOUT 10L /* 10 seconds. */ #define HTTP_CONNECT_TIMEOUT 10L /* 10 seconds. */
#define HTTP_LOW_SPEED_LIMIT 30L /* 30 bytes per second. */ #define HTTP_LOW_SPEED_LIMIT 30L /* 30 bytes per second. */
#define HTTP_LOW_SPEED_TIME HTTP_CONNECT_TIMEOUT #define HTTP_LOW_SPEED_TIME HTTP_CONNECT_TIMEOUT
#define HTTP_BUFFER_SIZE 131072L /* 128 KiB. */ #define HTTP_BUFFER_SIZE 131072L /* 128 KiB. */
#define GITHUB_URL "https://github.com" #define GITHUB_URL "https://github.com"
#define GITHUB_API_URL "https://api.github.com" #define GITHUB_API_URL "https://api.github.com"

View file

@ -42,6 +42,7 @@ namespace nxdt::views
void ProcessGameCardStatus(GameCardStatus gc_status); void ProcessGameCardStatus(GameCardStatus gc_status);
std::string GetFormattedSizeString(GameCardSizeFunc func); std::string GetFormattedSizeString(GameCardSizeFunc func);
std::string GetCardIdSetString(FsGameCardIdSet *card_id_set);
void PopulateList(void); void PopulateList(void);
public: public:

View file

@ -25,7 +25,8 @@
"lafw_version_value": "%lu or greater (%s)", "lafw_version_value": "%lu or greater (%s)",
"sdk_version": "SDK version", "sdk_version": "SDK version",
"compatibility_type": "Compatibility type", "compatibility_type": "Compatibility type",
"package_id": "Package ID" "package_id": "Package ID",
"card_id_set": "Card ID Set"
}, },
"dump_options": "Dump options", "dump_options": "Dump options",

View file

@ -269,6 +269,57 @@ end:
return success; return success;
} }
bool cnmtVerifyContentHash(ContentMetaContext *cnmt_ctx, NcaContext *nca_ctx, const u8 *hash)
{
if (!cnmtIsValidContext(cnmt_ctx) || !nca_ctx || !*(nca_ctx->content_id_str) || nca_ctx->content_type > NcmContentType_DeltaFragment || !nca_ctx->content_size || !hash)
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
/* Return right away if we're dealing with a Meta NCA. */
if (nca_ctx->content_type == NcmContentType_Meta) return true;
NcmPackagedContentInfo *packaged_content_info = NULL;
bool success = false;
/* Loop through all of our content info entries. */
for(u16 i = 0; i < cnmt_ctx->packaged_header->content_count; i++)
{
/* Check if we got a matching content ID. */
packaged_content_info = &(cnmt_ctx->packaged_content_info[i]);
if (!memcmp(packaged_content_info->info.content_id.c, nca_ctx->content_id.c, sizeof(nca_ctx->content_id.c))) break;
packaged_content_info = NULL;
}
if (!packaged_content_info)
{
LOG_MSG_ERROR("Unable to find CNMT content record 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);
goto end;
}
/* Verify content hash. */
success = (memcmp(packaged_content_info->hash, hash, SHA256_HASH_SIZE) == 0);
#if LOG_LEVEL <= LOG_LEVEL_ERROR
if (!success)
{
char got[SHA256_HASH_STR_SIZE] = {0}, expected[SHA256_HASH_STR_SIZE] = {0};
utilsGenerateHexString(got, sizeof(got), hash, SHA256_HASH_SIZE, true);
utilsGenerateHexString(expected, sizeof(expected), packaged_content_info->hash, SHA256_HASH_SIZE, true);
LOG_MSG_ERROR("Invalid hash for \"%s\" NCA! Got \"%s\", expected \"%s\" (title ID %016lX, size 0x%lX, type 0x%02X, ID offset 0x%02X).", nca_ctx->content_id_str, \
got, expected, cnmt_ctx->packaged_header->title_id, nca_ctx->content_size, nca_ctx->content_type, nca_ctx->id_offset);
}
#endif
end:
return success;
}
bool cnmtUpdateContentInfo(ContentMetaContext *cnmt_ctx, NcaContext *nca_ctx) bool cnmtUpdateContentInfo(ContentMetaContext *cnmt_ctx, NcaContext *nca_ctx)
{ {
if (!cnmtIsValidContext(cnmt_ctx) || !nca_ctx || !*(nca_ctx->content_id_str) || !*(nca_ctx->hash_str) || nca_ctx->content_type > NcmContentType_DeltaFragment || !nca_ctx->content_size) if (!cnmtIsValidContext(cnmt_ctx) || !nca_ctx || !*(nca_ctx->content_id_str) || !*(nca_ctx->hash_str) || nca_ctx->content_type > NcmContentType_DeltaFragment || !nca_ctx->content_size)
@ -302,7 +353,7 @@ bool cnmtUpdateContentInfo(ContentMetaContext *cnmt_ctx, NcaContext *nca_ctx)
} }
} }
if (!success) LOG_MSG_ERROR("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, \ if (!success) LOG_MSG_ERROR("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); cnmt_ctx->packaged_header->title_id, nca_ctx->content_size, nca_ctx->content_type, nca_ctx->id_offset);
return success; return success;
@ -368,7 +419,7 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_
u32 i, j; u32 i, j;
char *xml_buf = NULL; char *xml_buf = NULL;
u64 xml_buf_size = 0; u64 xml_buf_size = 0;
char digest_str[0x41] = {0}; char digest_str[SHA256_HASH_STR_SIZE] = {0};
u8 count = 0, content_meta_type = cnmt_ctx->packaged_header->content_meta_type; u8 count = 0, content_meta_type = cnmt_ctx->packaged_header->content_meta_type;
bool success = false, invalid_nca = false; bool success = false, invalid_nca = false;

View file

@ -78,17 +78,3 @@ Result fsDeviceOperatorGetGameCardDeviceCertificate(FsDeviceOperator *d, const F
return rc; return rc;
} }
Result fsDeviceOperatorGetGameCardIdSet(FsDeviceOperator *d, FsGameCardIdSet *out)
{
const 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;
}

View file

@ -297,7 +297,7 @@ bool gamecardGetSecurityInformation(GameCardSecurityInformation *out)
return ret; return ret;
} }
bool gamecardGetIdSet(FsGameCardIdSet *out) bool gamecardGetCardIdSet(FsGameCardIdSet *out)
{ {
bool ret = false; bool ret = false;
@ -305,7 +305,7 @@ bool gamecardGetIdSet(FsGameCardIdSet *out)
{ {
if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !out) break; if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !out) break;
Result rc = fsDeviceOperatorGetGameCardIdSet(&g_deviceOperator, out); Result rc = fsDeviceOperatorGetGameCardIdSet(&g_deviceOperator, out, sizeof(FsGameCardIdSet), (s64)sizeof(FsGameCardIdSet));
if (R_FAILED(rc)) LOG_MSG_ERROR("fsDeviceOperatorGetGameCardIdSet failed! (0x%X)", rc); if (R_FAILED(rc)) LOG_MSG_ERROR("fsDeviceOperatorGetGameCardIdSet failed! (0x%X)", rc);
ret = R_SUCCEEDED(rc); ret = R_SUCCEEDED(rc);
@ -405,7 +405,7 @@ bool gamecardGetTrimmedSize(u64 *out)
SCOPED_LOCK(&g_gameCardMutex) SCOPED_LOCK(&g_gameCardMutex)
{ {
ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out); ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out);
if (ret) *out = (sizeof(GameCardHeader) + GAMECARD_PAGE_OFFSET(g_gameCardHeader.valid_data_end_address)); if (ret) *out = (sizeof(GameCardHeader) + GAMECARD_PAGE_OFFSET(g_gameCardHeader.valid_data_end_page));
} }
return ret; return ret;
@ -947,7 +947,7 @@ static bool gamecardReadSecurityInformation(GameCardSecurityInformation *out)
{ {
if ((g_fsProgramMemory.data_size - offset) < sizeof(GameCardInitialData)) break; if ((g_fsProgramMemory.data_size - offset) < sizeof(GameCardInitialData)) break;
if (memcmp(g_fsProgramMemory.data + offset, &(g_gameCardHeader.package_id), sizeof(g_gameCardHeader.package_id)) != 0) continue; if (memcmp(g_fsProgramMemory.data + offset, g_gameCardHeader.package_id, sizeof(g_gameCardHeader.package_id)) != 0) continue;
sha256CalculateHash(tmp_hash, g_fsProgramMemory.data + offset, sizeof(GameCardInitialData)); sha256CalculateHash(tmp_hash, g_fsProgramMemory.data + offset, sizeof(GameCardInitialData));

View file

@ -115,6 +115,8 @@ static KeysNxKeyset g_nxKeyset = {0};
static bool g_latestMasterKeyAvailable = false; static bool g_latestMasterKeyAvailable = false;
static bool g_wipedSetCal = false;
bool keysLoadKeyset(void) bool keysLoadKeyset(void)
{ {
bool ret = false; bool ret = false;
@ -228,7 +230,7 @@ bool keysDecryptRsaOaepWrappedTitleKey(const void *rsa_wrapped_titlekey, void *o
SCOPED_LOCK(&g_keysetMutex) SCOPED_LOCK(&g_keysetMutex)
{ {
if (!g_keysetLoaded) break; if (!g_keysetLoaded || !g_wipedSetCal) break;
size_t out_keydata_size = 0; size_t out_keydata_size = 0;
u8 out_keydata[RSA2048_BYTES] = {0}; u8 out_keydata[RSA2048_BYTES] = {0};
@ -769,6 +771,7 @@ static bool keysGetDecryptedEticketRsaDeviceKey(void)
u32 public_exponent = 0; u32 public_exponent = 0;
Aes128CtrContext eticket_aes_ctx = {0}; Aes128CtrContext eticket_aes_ctx = {0};
EticketRsaDeviceKey *eticket_rsa_key = (EticketRsaDeviceKey*)g_eTicketRsaDeviceKey.key; EticketRsaDeviceKey *eticket_rsa_key = (EticketRsaDeviceKey*)g_eTicketRsaDeviceKey.key;
bool success = false;
/* Decrypt eTicket RSA device key. */ /* Decrypt eTicket RSA device key. */
aes128CtrContextCreate(&eticket_aes_ctx, g_nxKeyset.eticket_rsa_kek, eticket_rsa_key->ctr); aes128CtrContextCreate(&eticket_aes_ctx, g_nxKeyset.eticket_rsa_kek, eticket_rsa_key->ctr);
@ -779,18 +782,24 @@ static bool keysGetDecryptedEticketRsaDeviceKey(void)
public_exponent = __builtin_bswap32(eticket_rsa_key->public_exponent); public_exponent = __builtin_bswap32(eticket_rsa_key->public_exponent);
if (public_exponent != ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT) if (public_exponent != ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT)
{ {
LOG_MSG_ERROR("Invalid public exponent for decrypted eTicket RSA device key! Wrong keys? (0x%X).", public_exponent); if (public_exponent == 0)
return false; {
/* Bail out if we're dealing with a wiped calibration area. */
LOG_MSG_ERROR("eTicket RSA device key is empty! Personalized titlekey crypto won't be handled. Restore an eMMC backup or disable set:cal blanking options.");
success = g_wipedSetCal = true;
} else {
LOG_MSG_ERROR("Invalid public exponent for decrypted eTicket RSA device key! Wrong keys? (0x%X).", public_exponent);
}
goto end;
} }
/* Test RSA key pair. */ /* Test RSA key pair. */
if (!keysTestEticketRsaDeviceKey(&(eticket_rsa_key->public_exponent), eticket_rsa_key->private_exponent, eticket_rsa_key->modulus)) success = keysTestEticketRsaDeviceKey(&(eticket_rsa_key->public_exponent), eticket_rsa_key->private_exponent, eticket_rsa_key->modulus);
{ if (!success) LOG_MSG_ERROR("eTicket RSA device key test failed! Wrong keys?");
LOG_MSG_ERROR("eTicket RSA device key test failed! Wrong keys?");
return false;
}
return true; end:
return success;
} }
static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void *n) static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void *n)

View file

@ -531,7 +531,7 @@ void ncaWriteEncryptedHeaderDataToMemoryBuffer(NcaContext *ctx, void *buf, u64 b
} }
} }
void ncaUpdateContentIdAndHash(NcaContext *ctx, u8 hash[SHA256_HASH_SIZE]) void ncaUpdateContentIdAndHash(NcaContext *ctx, const u8 *hash)
{ {
if (!ctx) return; if (!ctx) return;

View file

@ -23,7 +23,7 @@
#include "pfs.h" #include "pfs.h"
#include "npdm.h" #include "npdm.h"
#define PFS_FULL_HEADER_ALIGNMENT 0x20 #define PFS_HEADER_PADDING_ALIGNMENT 0x20
bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx) bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx)
{ {
@ -244,7 +244,7 @@ bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemE
return true; return true;
} }
bool pfsAddEntryInformationToFileContext(PartitionFileSystemFileContext *ctx, const char *entry_name, u64 entry_size, u32 *out_entry_idx) bool pfsAddEntryInformationToImageContext(PartitionFileSystemImageContext *ctx, const char *entry_name, u64 entry_size, u32 *out_entry_idx)
{ {
if (!ctx || !entry_name || !*entry_name) if (!ctx || !entry_name || !*entry_name)
{ {
@ -304,7 +304,7 @@ bool pfsAddEntryInformationToFileContext(PartitionFileSystemFileContext *ctx, co
return true; return true;
} }
bool pfsUpdateEntryNameFromFileContext(PartitionFileSystemFileContext *ctx, u32 entry_idx, const char *new_entry_name) bool pfsUpdateEntryNameFromImageContext(PartitionFileSystemImageContext *ctx, u32 entry_idx, const char *new_entry_name)
{ {
if (!ctx || !ctx->header.entry_count || !ctx->header.name_table_size || !ctx->entries || !ctx->name_table || entry_idx >= ctx->header.entry_count || !new_entry_name || !*new_entry_name) if (!ctx || !ctx->header.entry_count || !ctx->header.name_table_size || !ctx->entries || !ctx->name_table || entry_idx >= ctx->header.entry_count || !new_entry_name || !*new_entry_name)
{ {
@ -329,7 +329,7 @@ bool pfsUpdateEntryNameFromFileContext(PartitionFileSystemFileContext *ctx, u32
return true; return true;
} }
bool pfsWriteFileContextHeaderToMemoryBuffer(PartitionFileSystemFileContext *ctx, void *buf, u64 buf_size, u64 *out_header_size) bool pfsWriteImageContextHeaderToMemoryBuffer(PartitionFileSystemImageContext *ctx, void *buf, u64 buf_size, u64 *out_header_size)
{ {
if (!ctx || !ctx->header.entry_count || !ctx->header.name_table_size || !ctx->entries || !ctx->name_table || !buf || !out_header_size) if (!ctx || !ctx->header.entry_count || !ctx->header.name_table_size || !ctx->entries || !ctx->name_table || !buf || !out_header_size)
{ {
@ -339,20 +339,20 @@ bool pfsWriteFileContextHeaderToMemoryBuffer(PartitionFileSystemFileContext *ctx
PartitionFileSystemHeader *header = &(ctx->header); PartitionFileSystemHeader *header = &(ctx->header);
u8 *buf_u8 = (u8*)buf; u8 *buf_u8 = (u8*)buf;
u64 header_size = 0, full_header_size = 0, block_offset = 0, block_size = 0; u64 header_size = 0, padded_header_size = 0, block_offset = 0, block_size = 0;
u32 padding_size = 0; u32 padding_size = 0;
/* Calculate header size. */ /* Calculate header size. */
header_size = (sizeof(PartitionFileSystemHeader) + (header->entry_count * sizeof(PartitionFileSystemEntry)) + header->name_table_size); header_size = (sizeof(PartitionFileSystemHeader) + (header->entry_count * sizeof(PartitionFileSystemEntry)) + header->name_table_size);
/* Calculate full header size and padding size. */ /* Calculate padded 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)); padded_header_size = (IS_ALIGNED(header_size, PFS_HEADER_PADDING_ALIGNMENT) ? ALIGN_UP(header_size + 1, PFS_HEADER_PADDING_ALIGNMENT) : ALIGN_UP(header_size, PFS_HEADER_PADDING_ALIGNMENT));
padding_size = (u32)(full_header_size - header_size); padding_size = (u32)(padded_header_size - header_size);
/* Check buffer size. */ /* Check buffer size. */
if (buf_size < full_header_size) if (buf_size < padded_header_size)
{ {
LOG_MSG_ERROR("Not enough space available in input buffer to write full Partition FS header! (got 0x%lX, need 0x%lX).", buf_size, full_header_size); LOG_MSG_ERROR("Not enough space available in input buffer to write full Partition FS header! (got 0x%lX, need 0x%lX).", buf_size, padded_header_size);
return false; return false;
} }
@ -374,7 +374,7 @@ bool pfsWriteFileContextHeaderToMemoryBuffer(PartitionFileSystemFileContext *ctx
memset(buf_u8 + block_offset, 0, padding_size); memset(buf_u8 + block_offset, 0, padding_size);
/* Update output header size. */ /* Update output header size. */
*out_header_size = full_header_size; *out_header_size = padded_header_size;
return true; return true;
} }

View file

@ -34,10 +34,10 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base
bool dump_fs_header = false, success = false; bool dump_fs_header = false, success = false;
/* Check if the base RomFS is missing (e.g. Fortnite, World of Tanks Blitz, etc.). */ /* Check if the base RomFS is missing (e.g. Fortnite, World of Tanks Blitz, etc.). */
bool missing_base_romfs = (base_nca_fs_ctx && (!base_nca_fs_ctx->enabled || (base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs && \ bool missing_base_romfs = (!base_nca_fs_ctx || !base_nca_fs_ctx->enabled || (base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs && \
base_nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs))); base_nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs));
if (!out || !base_nca_fs_ctx || (!patch_nca_fs_ctx && (missing_base_romfs || base_nca_fs_ctx->has_sparse_layer)) || \ if (!out || (!patch_nca_fs_ctx && (missing_base_romfs || base_nca_fs_ctx->has_sparse_layer)) || \
(!missing_base_romfs && (!(base_nca_ctx = base_nca_fs_ctx->nca_ctx) || (base_nca_ctx->format_version == NcaVersion_Nca0 && \ (!missing_base_romfs && (!(base_nca_ctx = 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_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || base_nca_fs_ctx->hash_type != NcaHashType_HierarchicalSha256)) || \
(base_nca_ctx->format_version != NcaVersion_Nca0 && (base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \ (base_nca_ctx->format_version != NcaVersion_Nca0 && (base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \
@ -54,7 +54,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base
romfsFreeContext(out); romfsFreeContext(out);
NcaStorageContext *base_storage_ctx = &(out->storage_ctx[0]), *patch_storage_ctx = &(out->storage_ctx[1]); 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); bool is_nca0_romfs = (base_nca_fs_ctx && base_nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs);
/* Initialize base NCA storage context. */ /* Initialize base NCA storage context. */
if (!missing_base_romfs && !ncaStorageInitializeContext(base_storage_ctx, base_nca_fs_ctx, NULL)) if (!missing_base_romfs && !ncaStorageInitializeContext(base_storage_ctx, base_nca_fs_ctx, NULL))

View file

@ -1241,7 +1241,7 @@ fallback:
{ {
strcat(app_name, "_"); strcat(app_name, "_");
cur_filename_len = strlen(app_name); cur_filename_len = strlen(app_name);
utilsGenerateHexString(app_name + cur_filename_len, sizeof(app_name) - cur_filename_len, &(gc_header.package_id), sizeof(gc_header.package_id), false); utilsGenerateHexString(app_name + cur_filename_len, sizeof(app_name) - cur_filename_len, gc_header.package_id, sizeof(gc_header.package_id), false);
} }
filename = strdup(app_name); filename = strdup(app_name);

View file

@ -848,7 +848,7 @@ static bool usbInitializeComms5x(void)
bos_desc->bLength = sizeof(struct usb_bos_descriptor); bos_desc->bLength = sizeof(struct usb_bos_descriptor);
bos_desc->bDescriptorType = USB_DT_BOS; bos_desc->bDescriptorType = USB_DT_BOS;
bos_desc->wTotalLength = USB_BOS_SIZE; bos_desc->wTotalLength = sizeof(bos);
bos_desc->bNumDeviceCaps = 2; /* USB 2.0 + USB 3.0. No extra capabilities for USB 1.x. */ bos_desc->bNumDeviceCaps = 2; /* USB 2.0 + USB 3.0. No extra capabilities for USB 1.x. */
usb2_ext_desc->bLength = sizeof(struct usb_2_0_extension_descriptor); usb2_ext_desc->bLength = sizeof(struct usb_2_0_extension_descriptor);
@ -961,7 +961,7 @@ static bool usbInitializeComms5x(void)
} }
/* Set Binary Object Store. */ /* Set Binary Object Store. */
rc = usbDsSetBinaryObjectStore(bos, USB_BOS_SIZE); rc = usbDsSetBinaryObjectStore(bos, sizeof(bos));
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG_ERROR("usbDsSetBinaryObjectStore failed! (0x%X).", rc); LOG_MSG_ERROR("usbDsSetBinaryObjectStore failed! (0x%X).", rc);

View file

@ -96,12 +96,28 @@ namespace nxdt::views
return std::string(strbuf); return std::string(strbuf);
} }
std::string GameCardTab::GetCardIdSetString(FsGameCardIdSet *card_id_set)
{
char card_id_set_str[0x20] = {0};
utilsGenerateHexString(card_id_set_str, sizeof(card_id_set_str), &(card_id_set->id1), sizeof(card_id_set->id1), true);
card_id_set_str[8] = ' ';
utilsGenerateHexString(card_id_set_str + 9, sizeof(card_id_set_str) - 9, &(card_id_set->id2), sizeof(card_id_set->id2), true);
card_id_set_str[17] = ' ';
utilsGenerateHexString(card_id_set_str + 18, sizeof(card_id_set_str) - 18, &(card_id_set->id3), sizeof(card_id_set->id3), true);
return std::string(card_id_set_str);
}
void GameCardTab::PopulateList(void) void GameCardTab::PopulateList(void)
{ {
TitleApplicationMetadata **app_metadata = NULL; TitleApplicationMetadata **app_metadata = NULL;
u32 app_metadata_count = 0; u32 app_metadata_count = 0;
GameCardHeader card_header = {0}; GameCardHeader card_header = {0};
GameCardInfo card_info = {0}; GameCardInfo card_info = {0};
FsGameCardIdSet card_id_set = {0};
bool update_focused_view = this->IsListItemFocused(); bool update_focused_view = this->IsListItemFocused();
int focus_stack_index = this->GetFocusStackViewIndex(); int focus_stack_index = this->GetFocusStackViewIndex();
@ -123,6 +139,12 @@ namespace nxdt::views
/* Display the applications that are part of the inserted gamecard. */ /* Display the applications that are part of the inserted gamecard. */
this->list->addView(new brls::Header("gamecard_tab/list/user_titles/header"_i18n)); this->list->addView(new brls::Header("gamecard_tab/list/user_titles/header"_i18n));
/* Information about how to handle user titles. */
brls::Label *user_titles_info = new brls::Label(brls::LabelStyle::DESCRIPTION, i18n::getStr("gamecard_tab/list/user_titles/info"_i18n, \
"root_view/tabs/user_titles"_i18n), true);
user_titles_info->setHorizontalAlign(NVG_ALIGN_CENTER);
this->list->addView(user_titles_info);
/* Populate list. */ /* Populate list. */
for(u32 i = 0; i < app_metadata_count; i++) for(u32 i = 0; i < app_metadata_count; i++)
{ {
@ -131,12 +153,6 @@ namespace nxdt::views
this->list->addView(title); this->list->addView(title);
} }
/* Information about how to handle user titles. */
brls::Label *user_titles_info = new brls::Label(brls::LabelStyle::DESCRIPTION, i18n::getStr("gamecard_tab/list/user_titles/info"_i18n, \
"root_view/tabs/user_titles"_i18n), true);
user_titles_info->setHorizontalAlign(NVG_ALIGN_CENTER);
this->list->addView(user_titles_info);
/* Free application metadata array. */ /* Free application metadata array. */
free(app_metadata); free(app_metadata);
} }
@ -153,6 +169,7 @@ namespace nxdt::views
brls::TableRow *sdk_version = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/sdk_version"_i18n); brls::TableRow *sdk_version = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/sdk_version"_i18n);
brls::TableRow *compatibility_type = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/compatibility_type"_i18n); brls::TableRow *compatibility_type = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/compatibility_type"_i18n);
brls::TableRow *package_id = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/package_id"_i18n); brls::TableRow *package_id = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/package_id"_i18n);
brls::TableRow *card_id_set_row = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/card_id_set"_i18n);
capacity->setValue(this->GetFormattedSizeString(&gamecardGetRomCapacity)); capacity->setValue(this->GetFormattedSizeString(&gamecardGetRomCapacity));
total_size->setValue(this->GetFormattedSizeString(&gamecardGetTotalSize)); total_size->setValue(this->GetFormattedSizeString(&gamecardGetTotalSize));
@ -160,6 +177,7 @@ namespace nxdt::views
gamecardGetHeader(&card_header); gamecardGetHeader(&card_header);
gamecardGetDecryptedCardInfoArea(&card_info); gamecardGetDecryptedCardInfoArea(&card_info);
gamecardGetCardIdSet(&card_id_set);
const SystemVersion upp_version = card_info.upp_version.system_version; const SystemVersion upp_version = card_info.upp_version.system_version;
@ -213,7 +231,11 @@ namespace nxdt::views
compat_type >= GameCardCompatibilityType_Count ? "generic/unknown"_i18n : gamecardGetCompatibilityTypeString(compat_type), \ compat_type >= GameCardCompatibilityType_Count ? "generic/unknown"_i18n : gamecardGetCompatibilityTypeString(compat_type), \
compat_type)); compat_type));
package_id->setValue(fmt::format("{:016X}", card_header.package_id)); char package_id_str[0x11] = {0};
utilsGenerateHexString(package_id_str, sizeof(package_id_str), card_header.package_id, sizeof(card_header.package_id), true);
package_id->setValue(std::string(package_id_str));
card_id_set_row->setValue(this->GetCardIdSetString(&card_id_set));
this->list->addView(properties_table); this->list->addView(properties_table);