diff --git a/.github/workflows/rewrite.yml b/.github/workflows/rewrite.yml index 6f78de9..12d0db8 100644 --- a/.github/workflows/rewrite.yml +++ b/.github/workflows/rewrite.yml @@ -63,8 +63,8 @@ jobs: ./build.sh #- name: Build nxdumptool-rewrite GUI binary + # working-directory: nxdumptool # run: | - # cd "$GITHUB_WORKSPACE/nxdumptool" # make -j$(nproc) - uses: actions/upload-artifact@v3 @@ -94,14 +94,15 @@ jobs: - name: Upload artifact to prerelease uses: ncipollo/release-action@v1 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 tag: "rewrite-prerelease" + commit: "rewrite" updateOnlyUnreleased: True - # remove old artifacts & replace with new ones + # Remove old artifacts and replace with new ones. removeArtifacts: True replacesArtifacts: True - # update preference + # Update preferences. allowUpdates: True omitBody: True omitBodyDuringUpdate: True diff --git a/code_templates/nxdt_rw_poc.c b/code_templates/nxdt_rw_poc.c index 898bef9..7f0ff52 100644 --- a/code_templates/nxdt_rw_poc.c +++ b/code_templates/nxdt_rw_poc.c @@ -143,6 +143,7 @@ static u64 utilsGetButtonsHeld(void); static void utilsWaitForButtonPress(u64 flag); static void consolePrint(const char *text, ...); +static void consolePrintReversedColors(const char *text, ...); static void consoleRefresh(void); static u32 menuGetElementCount(const Menu *menu); @@ -153,6 +154,8 @@ void updateStorageList(void); void freeTitleList(Menu *menu); 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 updateNcaList(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)) { - consolePrint("press l/zl and/or r/zr to change the selected title\n"); - consolePrint("title: %u / %u\n", title_info_idx, title_info_count); + consolePrintReversedColors("press l/zl/r/zr to change the selected title\n"); + consolePrintReversedColors("title: %u / %u\n", title_info_idx + 1, title_info_count); consolePrint("______________________________\n\n"); } @@ -988,7 +991,8 @@ int main(int argc, char *argv[]) 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"); } @@ -1123,6 +1127,8 @@ int main(int argc, char *argv[]) if (title_info) { + title_info = getLatestTitleInfo(title_info, &title_info_idx, &title_info_count); + if (child_menu->id == MenuId_Nca) { 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) - { - title_info_count = titleGetCountFromInfoBlock(title_info); - title_info_idx = 1; - } } else { if (cur_menu->id == MenuId_SystemTitles) { @@ -1446,6 +1446,22 @@ static void consolePrint(const char *text, ...) 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) { mutexLock(&g_conMutex); @@ -1616,6 +1632,49 @@ end: 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) { /* Free all previously allocated data. */ @@ -1966,7 +2025,7 @@ static bool waitForGameCard(void) if (status != GameCardStatus_InsertedAndInfoLoaded) { - consolePrint("press any button\n"); + consolePrint("press any button to go back\n"); utilsWaitForButtonPress(0); return false; } @@ -2502,7 +2561,7 @@ static bool saveGameCardIdSet(void *userdata) u32 crc = 0; char *filename = NULL; - if (!gamecardGetIdSet(&id_set)) + if (!gamecardGetCardIdSet(&id_set)) { consolePrint("failed to get gamecard id set\n"); goto end; @@ -2987,8 +3046,9 @@ static bool saveNintendoContentArchive(void *userdata) 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(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); if (!filename) goto end; @@ -3136,14 +3196,8 @@ static bool saveNintendoContentArchiveFsSection(void *userdata) } /* 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)); if (!base_patch_nca_ctx) { @@ -3160,6 +3214,12 @@ static bool saveNintendoContentArchiveFsSection(void *userdata) /* Use a matching NCA FS section entry. */ 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) @@ -3233,8 +3293,7 @@ static bool saveRawPartitionFsSection(PartitionFileSystemContext *pfs_ctx, bool filename = generateOutputLayeredFsFileName(title_id + nca_ctx->id_offset, NULL, "exefs.nsp"); } else { 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, \ - nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx)); + snprintf(path, MAX_ELEMENTS(path), "/%s #%u/%u.nsp", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx); TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); 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"); } else { 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, \ - nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx)); + snprintf(path, MAX_ELEMENTS(path), "/%s #%u/%u.bin", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx); TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); 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"); } else { 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, \ - nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx)); + snprintf(pfs_path, MAX_ELEMENTS(pfs_path), "/%s #%u/%u", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx); TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); 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"); } else { 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, \ - nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx)); + snprintf(romfs_path, MAX_ELEMENTS(romfs_path), "/%s #%u/%u", titleGetNcmContentTypeName(nca_ctx->content_type), nca_ctx->id_offset, nca_fs_ctx->section_idx); TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); 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. */ 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) { condvarWakeAll(&g_writeCondvar); @@ -4758,15 +4814,15 @@ static void nspThreadFunc(void *arg) u8 *raw_cert_chain = NULL; u64 raw_cert_chain_size = 0; - PartitionFileSystemFileContext pfs_file_ctx = {0}; - pfsInitializeFileContext(&pfs_file_ctx); + PartitionFileSystemImageContext pfs_img_ctx = {0}; + pfsInitializeImageContext(&pfs_img_ctx); char entry_name[64] = {0}; u64 nsp_header_size = 0, nsp_size = 0, nsp_offset = 0; char *tmp_name = NULL; - Sha256Context sha256_ctx = {0}; - u8 sha256_hash[SHA256_HASH_SIZE] = {0}; + Sha256Context clean_sha256_ctx = {0}, dirty_sha256_ctx = {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; @@ -5023,7 +5079,7 @@ static void nspThreadFunc(void *arg) 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"); - 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); goto end; @@ -5034,7 +5090,7 @@ static void nspThreadFunc(void *arg) if (generate_authoringtool_data) { 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); goto end; @@ -5055,7 +5111,7 @@ static void nspThreadFunc(void *arg) { ProgramInfoContext *cur_program_info_ctx = (ProgramInfoContext*)cur_nca_ctx->content_type_ctx; 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; } case NcmContentType_Control: @@ -5066,7 +5122,7 @@ static void nspThreadFunc(void *arg) { 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)); - 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); goto end; @@ -5074,14 +5130,14 @@ static void nspThreadFunc(void *arg) } 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; } case NcmContentType_LegalInformation: { LegalInfoContext *cur_legal_info_ctx = (LegalInfoContext*)cur_nca_ctx->content_type_ctx; 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; } default: @@ -5099,28 +5155,28 @@ static void nspThreadFunc(void *arg) if (retrieve_tik_cert) { 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); goto end; } 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); goto end; } } - // write buffer to memory buffer - if (!pfsWriteFileContextHeaderToMemoryBuffer(&pfs_file_ctx, buf, BLOCK_SIZE, &nsp_header_size)) + // write pfs header to memory buffer + if (!pfsWriteImageContextHeaderToMemoryBuffer(&pfs_img_ctx, buf, BLOCK_SIZE, &nsp_header_size)) { consolePrint("pfs write header to mem #1 failed\n"); 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); consoleRefresh(); @@ -5183,8 +5239,8 @@ static void nspThreadFunc(void *arg) NcaContext *cur_nca_ctx = &(nca_ctx[i]); u64 blksize = BLOCK_SIZE; - memset(&sha256_ctx, 0, sizeof(Sha256Context)); - sha256ContextCreate(&sha256_ctx); + sha256ContextCreate(&clean_sha256_ctx); + sha256ContextCreate(&dirty_sha256_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) { - tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, i); + tmp_name = pfsGetEntryNameByIndexFromImageContext(&pfs_img_ctx, i); if (!usbSendFileProperties(cur_nca_ctx->content_size, tmp_name)) { consolePrint("usb send file properties \"%s\" failed\n", tmp_name); @@ -5225,6 +5281,9 @@ static void nspThreadFunc(void *arg) goto end; } + // update clean hash calculation + sha256ContextUpdate(&clean_sha256_ctx, buf, blksize); + if (dirty_header) { // 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); } - // update hash calculation - sha256ContextUpdate(&sha256_ctx, buf, blksize); + // update dirty hash calculation + sha256ContextUpdate(&dirty_sha256_ctx, buf, blksize); // write nca chunk if (dev_idx == 1) @@ -5266,24 +5325,35 @@ static void nspThreadFunc(void *arg) } } - // get hash - sha256ContextGetHash(&sha256_ctx, sha256_hash); + // get hashes + sha256ContextGetHash(&clean_sha256_ctx, clean_sha256_hash); + sha256ContextGetHash(&dirty_sha256_ctx, dirty_sha256_hash); - // update content id and hash - ncaUpdateContentIdAndHash(cur_nca_ctx, sha256_hash); - - // update cnmt - if (!cnmtUpdateContentInfo(&cnmt_ctx, cur_nca_ctx)) + // verify content hash + if (!cnmtVerifyContentHash(&cnmt_ctx, cur_nca_ctx, clean_sha256_hash)) { - consolePrint("cnmt update content info failed\n"); + consolePrint("sha256 checksum mismatch for nca \"%s\"\n", cur_nca_ctx->content_id_str); goto end; } - // update pfs entry name - if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, i, cur_nca_ctx->content_id_str)) + if (memcmp(clean_sha256_hash, dirty_sha256_hash, SHA256_HASH_SIZE) != 0) { - consolePrint("pfs update entry name failed for nca \"%s\"\n", cur_nca_ctx->content_id_str); - goto end; + // update content id and hash + 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 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)) { 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; // 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"); goto end; @@ -5353,7 +5423,7 @@ static void nspThreadFunc(void *arg) // write icon 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)) { 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; // 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); goto end; @@ -5390,7 +5460,7 @@ static void nspThreadFunc(void *arg) // write xml 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)) { 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; // 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); goto end; @@ -5416,7 +5486,7 @@ static void nspThreadFunc(void *arg) // write ticket 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)) { consolePrint("send \"%s\" failed\n", tmp_name); @@ -5432,7 +5502,7 @@ static void nspThreadFunc(void *arg) // write cert 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)) { consolePrint("send \"%s\" failed\n", tmp_name); @@ -5447,7 +5517,7 @@ static void nspThreadFunc(void *arg) } // 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"); goto end; @@ -5492,7 +5562,7 @@ end: } } - pfsFreeFileContext(&pfs_file_ctx); + pfsFreeImageContext(&pfs_img_ctx); if (raw_cert_chain) free(raw_cert_chain); diff --git a/include/core/cnmt.h b/include/core/cnmt.h index c3aa201..919ac1c 100644 --- a/include/core/cnmt.h +++ b/include/core/cnmt.h @@ -318,6 +318,9 @@ typedef struct { /// Initializes a ContentMetaContext using a previously initialized NcaContext (which must belong to a Meta NCA). 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. bool cnmtUpdateContentInfo(ContentMetaContext *cnmt_ctx, NcaContext *nca_ctx); diff --git a/include/core/fs_ext.h b/include/core/fs_ext.h index 30a5030..3524d3c 100644 --- a/include/core/fs_ext.h +++ b/include/core/fs_ext.h @@ -45,19 +45,57 @@ typedef struct { 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 { - u8 maker_code; ///< Usually 0xC2 (Macronix). + u8 maker_code; ///< FsCardId1MakerCode. u8 memory_capacity; ///< Matches GameCardRomSize. - u8 reserved; ///< Known values: 0x06, 0x09, 0x0A. - u8 memory_type; ///< Usually 0x21. + u8 reserved; ///< Known values: 0x00, 0x01, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x80. + u8 memory_type; ///< FsCardId1MemoryType. } FsCardId1; 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 { - u8 card_security_number; ///< Usually 0x02. - u8 card_type; ///< Usually 0x00. - u8 reserved[0x2]; ///< Usually filled with zeroes. + u8 sel_t1_key; ///< Matches sel_t1_key value from GameCardHeader (usually 0x02). + u8 card_type; ///< FsCardId2CardType. + u8 reserved[0x2]; ///< Usually filled with zeroes. } FsCardId2; NXDT_ASSERT(FsCardId2, 0x4); @@ -84,7 +122,6 @@ Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier *out); /// IDeviceOperator. 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 fsDeviceOperatorGetGameCardIdSet(FsDeviceOperator *d, FsGameCardIdSet *out); #ifdef __cplusplus } diff --git a/include/core/gamecard.h b/include/core/gamecard.h index d6cb7af..00e9b81 100644 --- a/include/core/gamecard.h +++ b/include/core/gamecard.h @@ -45,7 +45,7 @@ typedef struct { union { u8 value[0x10]; struct { - u64 package_id; ///< Matches package_id from GameCardHeader. + u8 package_id[0x8]; ///< Matches package_id from GameCardHeader. u8 reserved[0x8]; ///< Just zeroes. }; }; @@ -200,14 +200,14 @@ NXDT_ASSERT(GameCardInfo, 0x70); typedef struct { u8 signature[0x100]; ///< RSA-2048-PSS with SHA-256 signature over the rest of the header. u32 magic; ///< "HEAD". - u32 rom_area_start_page_address; ///< Expressed in GAMECARD_PAGE_SIZE units. - u32 backup_area_start_page_address; ///< Always 0xFFFFFFFF. + u32 rom_area_start_page; ///< Expressed in GAMECARD_PAGE_SIZE units. + u32 backup_area_start_page; ///< Always 0xFFFFFFFF. GameCardKeyIndex key_index; u8 rom_size; ///< GameCardRomSize. - u8 header_version; ///< Always 0. + u8 version; ///< Always 0x00. u8 flags; ///< GameCardFlags. - u64 package_id; ///< Used for challenge-response authentication. - u32 valid_data_end_address; ///< Expressed in GAMECARD_PAGE_SIZE units. + u8 package_id[0x8]; ///< Used for challenge-response authentication. + u32 valid_data_end_page; ///< Expressed in GAMECARD_PAGE_SIZE units. u8 reserved[0x4]; 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. @@ -215,9 +215,9 @@ typedef struct { u8 partition_fs_header_hash[SHA256_HASH_SIZE]; u8 initial_data_hash[SHA256_HASH_SIZE]; u32 sel_sec; ///< GameCardSelSec. - u32 sel_t1_key; ///< Always 2. - u32 sel_key; ///< Always 0. - u32 lim_area; ///< Expressed in GAMECARD_PAGE_SIZE units. + u32 sel_t1_key; ///< Always 0x02. + u32 sel_key; ///< Always 0x00. + u32 lim_area_page; ///< Expressed in GAMECARD_PAGE_SIZE units. GameCardInfo card_info; } GameCardHeader; @@ -291,7 +291,7 @@ bool gamecardGetSecurityInformation(GameCardSecurityInformation *out); /// Fills the provided FsGameCardIdSet pointer. /// 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. /// 'out_lafw_blob' or 'out_lafw_version' may be set to NULL, but at least one of them must be a valid pointer. diff --git a/include/core/nca.h b/include/core/nca.h index 464b437..0d4416d 100644 --- a/include/core/nca.h +++ b/include/core/nca.h @@ -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. char content_id_str[0x21]; 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 content_type; ///< NcmContentType. 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); /// 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. const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx); diff --git a/include/core/pfs.h b/include/core/pfs.h index 8ffbe9a..2f98268 100644 --- a/include/core/pfs.h +++ b/include/core/pfs.h @@ -61,13 +61,13 @@ typedef struct { u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table. } PartitionFileSystemContext; -/// Used with Partition FS images (e.g. NSPs). +/// Used to generate Partition FS images (e.g. NSPs). typedef struct { PartitionFileSystemHeader header; ///< Partition FS header. Holds the entry count and name table size. PartitionFileSystemEntry *entries; ///< Partition FS entries. char *name_table; ///< Name table. u64 fs_size; ///< Partition FS data size. Updated each time a new entry is added. -} PartitionFileSystemFileContext; +} PartitionFileSystemImageContext; /// Initializes a Partition FS context. 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. 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. -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. -bool pfsUpdateEntryNameFromFileContext(PartitionFileSystemFileContext *ctx, u32 entry_idx, const char *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 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. -bool pfsWriteFileContextHeaderToMemoryBuffer(PartitionFileSystemFileContext *ctx, void *buf, u64 buf_size, u64 *out_header_size); +/// Generates a full Partition FS header from an existing PartitionFileSystemImageContext and writes it to the provided memory buffer. +bool pfsWriteImageContextHeaderToMemoryBuffer(PartitionFileSystemImageContext *ctx, void *buf, u64 buf_size, u64 *out_header_size); /// Miscellaneous functions. @@ -165,35 +165,35 @@ NX_INLINE void pfsFreeEntryPatch(NcaHierarchicalSha256Patch *patch) ncaFreeHierarchicalSha256Patch(patch); } -NX_INLINE void pfsFreeFileContext(PartitionFileSystemFileContext *ctx) +NX_INLINE void pfsFreeImageContext(PartitionFileSystemImageContext *ctx) { if (!ctx) return; if (ctx->entries) free(ctx->entries); 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; - pfsFreeFileContext(ctx); + pfsFreeImageContext(ctx); 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); } -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]); } -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; return (ctx->name_table + fs_entry->name_offset); } diff --git a/include/core/romfs.h b/include/core/romfs.h index dcb1286..9500c16 100644 --- a/include/core/romfs.h +++ b/include/core/romfs.h @@ -139,7 +139,7 @@ typedef enum { } RomFileSystemPathIllegalCharReplaceType; /// 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. bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base_nca_fs_ctx, NcaFsSectionContext *patch_nca_fs_ctx); diff --git a/include/defines.h b/include/defines.h index 5e564be..1cfb74f 100644 --- a/include/defines.h +++ b/include/defines.h @@ -60,7 +60,9 @@ /* 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 BOOT_SYSMODULE_TID (u64)0x0100000000000005 @@ -69,7 +71,7 @@ #define SYSTEM_UPDATE_TID (u64)0x0100000000000816 #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 CRLF "\r\n" @@ -93,22 +95,22 @@ #define NRO_PATH DEVOPTAB_SDMC_DEVICE APP_BASE_PATH NRO_NAME #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 DEV_KEYS_FILE_PATH DEVOPTAB_SDMC_DEVICE HBMENU_BASE_PATH "dev.keys" /* Location used by Lockpick_RCM for development 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 LOG_FILE_NAME APP_TITLE ".log" -#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_BUF_SIZE 0x400000 /* 4 MiB. */ +#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 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_CONNECT_TIMEOUT 10L /* 10 seconds. */ -#define HTTP_LOW_SPEED_LIMIT 30L /* 30 bytes per second. */ +#define HTTP_CONNECT_TIMEOUT 10L /* 10 seconds. */ +#define HTTP_LOW_SPEED_LIMIT 30L /* 30 bytes per second. */ #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_API_URL "https://api.github.com" diff --git a/include/gamecard_tab.hpp b/include/gamecard_tab.hpp index 83b63e8..ad392d6 100644 --- a/include/gamecard_tab.hpp +++ b/include/gamecard_tab.hpp @@ -42,6 +42,7 @@ namespace nxdt::views void ProcessGameCardStatus(GameCardStatus gc_status); std::string GetFormattedSizeString(GameCardSizeFunc func); + std::string GetCardIdSetString(FsGameCardIdSet *card_id_set); void PopulateList(void); public: diff --git a/romfs/i18n/en-US/gamecard_tab.json b/romfs/i18n/en-US/gamecard_tab.json index cc69084..5cbf3c5 100644 --- a/romfs/i18n/en-US/gamecard_tab.json +++ b/romfs/i18n/en-US/gamecard_tab.json @@ -25,7 +25,8 @@ "lafw_version_value": "%lu or greater (%s)", "sdk_version": "SDK version", "compatibility_type": "Compatibility type", - "package_id": "Package ID" + "package_id": "Package ID", + "card_id_set": "Card ID Set" }, "dump_options": "Dump options", diff --git a/source/core/cnmt.c b/source/core/cnmt.c index 38499f4..c164810 100644 --- a/source/core/cnmt.c +++ b/source/core/cnmt.c @@ -269,6 +269,57 @@ end: 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) { 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); return success; @@ -368,7 +419,7 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_ u32 i, j; char *xml_buf = NULL; 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; bool success = false, invalid_nca = false; diff --git a/source/core/fs_ext.c b/source/core/fs_ext.c index 05bab2a..2bf77de 100644 --- a/source/core/fs_ext.c +++ b/source/core/fs_ext.c @@ -78,17 +78,3 @@ Result fsDeviceOperatorGetGameCardDeviceCertificate(FsDeviceOperator *d, const F 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; -} diff --git a/source/core/gamecard.c b/source/core/gamecard.c index 8d4de3b..4494e7d 100644 --- a/source/core/gamecard.c +++ b/source/core/gamecard.c @@ -297,7 +297,7 @@ bool gamecardGetSecurityInformation(GameCardSecurityInformation *out) return ret; } -bool gamecardGetIdSet(FsGameCardIdSet *out) +bool gamecardGetCardIdSet(FsGameCardIdSet *out) { bool ret = false; @@ -305,7 +305,7 @@ bool gamecardGetIdSet(FsGameCardIdSet *out) { 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); ret = R_SUCCEEDED(rc); @@ -405,7 +405,7 @@ bool gamecardGetTrimmedSize(u64 *out) SCOPED_LOCK(&g_gameCardMutex) { 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; @@ -947,7 +947,7 @@ static bool gamecardReadSecurityInformation(GameCardSecurityInformation *out) { 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)); diff --git a/source/core/keys.c b/source/core/keys.c index 9b7312e..278e362 100644 --- a/source/core/keys.c +++ b/source/core/keys.c @@ -115,6 +115,8 @@ static KeysNxKeyset g_nxKeyset = {0}; static bool g_latestMasterKeyAvailable = false; +static bool g_wipedSetCal = false; + bool keysLoadKeyset(void) { bool ret = false; @@ -228,7 +230,7 @@ bool keysDecryptRsaOaepWrappedTitleKey(const void *rsa_wrapped_titlekey, void *o SCOPED_LOCK(&g_keysetMutex) { - if (!g_keysetLoaded) break; + if (!g_keysetLoaded || !g_wipedSetCal) break; size_t out_keydata_size = 0; u8 out_keydata[RSA2048_BYTES] = {0}; @@ -769,6 +771,7 @@ static bool keysGetDecryptedEticketRsaDeviceKey(void) u32 public_exponent = 0; Aes128CtrContext eticket_aes_ctx = {0}; EticketRsaDeviceKey *eticket_rsa_key = (EticketRsaDeviceKey*)g_eTicketRsaDeviceKey.key; + bool success = false; /* Decrypt eTicket RSA device key. */ 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); 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); - return false; + if (public_exponent == 0) + { + /* 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. */ - if (!keysTestEticketRsaDeviceKey(&(eticket_rsa_key->public_exponent), eticket_rsa_key->private_exponent, eticket_rsa_key->modulus)) - { - LOG_MSG_ERROR("eTicket RSA device key test failed! Wrong keys?"); - return false; - } + 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?"); - return true; +end: + return success; } static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void *n) diff --git a/source/core/nca.c b/source/core/nca.c index fd0e5dd..9b1a123 100644 --- a/source/core/nca.c +++ b/source/core/nca.c @@ -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; diff --git a/source/core/pfs.c b/source/core/pfs.c index 8f91515..68f09a6 100644 --- a/source/core/pfs.c +++ b/source/core/pfs.c @@ -23,7 +23,7 @@ #include "pfs.h" #include "npdm.h" -#define PFS_FULL_HEADER_ALIGNMENT 0x20 +#define PFS_HEADER_PADDING_ALIGNMENT 0x20 bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx) { @@ -244,7 +244,7 @@ bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemE 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) { @@ -304,7 +304,7 @@ bool pfsAddEntryInformationToFileContext(PartitionFileSystemFileContext *ctx, co 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) { @@ -329,7 +329,7 @@ bool pfsUpdateEntryNameFromFileContext(PartitionFileSystemFileContext *ctx, u32 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) { @@ -339,20 +339,20 @@ bool pfsWriteFileContextHeaderToMemoryBuffer(PartitionFileSystemFileContext *ctx PartitionFileSystemHeader *header = &(ctx->header); 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; /* 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); + /* Calculate padded header size and padding size. */ + 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)(padded_header_size - header_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; } @@ -374,7 +374,7 @@ bool pfsWriteFileContextHeaderToMemoryBuffer(PartitionFileSystemFileContext *ctx memset(buf_u8 + block_offset, 0, padding_size); /* Update output header size. */ - *out_header_size = full_header_size; + *out_header_size = padded_header_size; return true; } diff --git a/source/core/romfs.c b/source/core/romfs.c index 381b7ce..54193d4 100644 --- a/source/core/romfs.c +++ b/source/core/romfs.c @@ -34,10 +34,10 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base bool dump_fs_header = false, success = false; /* 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 && \ - base_nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs))); + 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)); - 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 && \ (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 || \ @@ -54,7 +54,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base 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); + bool is_nca0_romfs = (base_nca_fs_ctx && base_nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs); /* Initialize base NCA storage context. */ if (!missing_base_romfs && !ncaStorageInitializeContext(base_storage_ctx, base_nca_fs_ctx, NULL)) diff --git a/source/core/title.c b/source/core/title.c index d9edc5d..aa0e871 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -1241,7 +1241,7 @@ fallback: { strcat(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); diff --git a/source/core/usb.c b/source/core/usb.c index 84f9017..5ff9821 100644 --- a/source/core/usb.c +++ b/source/core/usb.c @@ -848,7 +848,7 @@ static bool usbInitializeComms5x(void) bos_desc->bLength = sizeof(struct usb_bos_descriptor); 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. */ usb2_ext_desc->bLength = sizeof(struct usb_2_0_extension_descriptor); @@ -961,7 +961,7 @@ static bool usbInitializeComms5x(void) } /* Set Binary Object Store. */ - rc = usbDsSetBinaryObjectStore(bos, USB_BOS_SIZE); + rc = usbDsSetBinaryObjectStore(bos, sizeof(bos)); if (R_FAILED(rc)) { LOG_MSG_ERROR("usbDsSetBinaryObjectStore failed! (0x%X).", rc); diff --git a/source/gamecard_tab.cpp b/source/gamecard_tab.cpp index ea6e1b5..4f0b5d0 100644 --- a/source/gamecard_tab.cpp +++ b/source/gamecard_tab.cpp @@ -96,12 +96,28 @@ namespace nxdt::views 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) { TitleApplicationMetadata **app_metadata = NULL; u32 app_metadata_count = 0; GameCardHeader card_header = {0}; GameCardInfo card_info = {0}; + FsGameCardIdSet card_id_set = {0}; bool update_focused_view = this->IsListItemFocused(); int focus_stack_index = this->GetFocusStackViewIndex(); @@ -123,6 +139,12 @@ namespace nxdt::views /* Display the applications that are part of the inserted gamecard. */ 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. */ for(u32 i = 0; i < app_metadata_count; i++) { @@ -131,12 +153,6 @@ namespace nxdt::views 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(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 *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 *card_id_set_row = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/card_id_set"_i18n); capacity->setValue(this->GetFormattedSizeString(&gamecardGetRomCapacity)); total_size->setValue(this->GetFormattedSizeString(&gamecardGetTotalSize)); @@ -160,6 +177,7 @@ namespace nxdt::views gamecardGetHeader(&card_header); gamecardGetDecryptedCardInfoArea(&card_info); + gamecardGetCardIdSet(&card_id_set); 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)); - 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);