From e6bb15d608854de498938ff7db1367d513748ac5 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Wed, 14 Oct 2020 09:23:49 -0400 Subject: [PATCH] Implemented BFTTF font loading and decoding. Some people may call me a madman but I don't want to see any more nxdumptool screenshots with a Disney-like font. *war flashbacks* --- code_templates/system_title_dumper.c | 3 +- code_templates/usb_romfs_dumper.c | 5 +- source/bfttf.c | 230 +++++++++++++++++++++++++++ source/bfttf.h | 55 +++++++ source/bktr.c | 3 + source/nca.c | 16 +- source/nca.h | 4 +- source/pfs.c | 4 +- source/romfs.c | 4 +- source/services.c | 7 - source/utils.c | 11 ++ 11 files changed, 321 insertions(+), 21 deletions(-) create mode 100644 source/bfttf.c create mode 100644 source/bfttf.h diff --git a/code_templates/system_title_dumper.c b/code_templates/system_title_dumper.c index 8fc5fa8..085821e 100644 --- a/code_templates/system_title_dumper.c +++ b/code_templates/system_title_dumper.c @@ -247,7 +247,6 @@ int main(int argc, char *argv[]) char nca_id_str[0x21] = {0}; NcaContext *nca_ctx = NULL; - Ticket tik = {0}; app_metadata = titleGetApplicationMetadataEntries(true, &app_count); if (!app_metadata || !app_count) @@ -357,7 +356,7 @@ int main(int argc, char *argv[]) } else if (menu == 2) { - if (!ncaInitializeContext(nca_ctx, cur_title_info->storage_id, 0, &(cur_title_info->content_infos[nca_idx]), &tik)) + if (!ncaInitializeContext(nca_ctx, cur_title_info->storage_id, 0, &(cur_title_info->content_infos[nca_idx]), NULL)) { consolePrint("nca initialize ctx failed\n"); error = true; diff --git a/code_templates/usb_romfs_dumper.c b/code_templates/usb_romfs_dumper.c index df6f9e1..453b275 100644 --- a/code_templates/usb_romfs_dumper.c +++ b/code_templates/usb_romfs_dumper.c @@ -329,7 +329,6 @@ int main(int argc, char *argv[]) u8 *buf = NULL; NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL; - Ticket base_tik = {0}, update_tik = {0}; RomFileSystemContext romfs_ctx = {0}; BktrContext bktr_ctx = {0}; @@ -486,7 +485,7 @@ int main(int argc, char *argv[]) consolePrint("selected title:\n%s (%016lX)\n\n", app_metadata[selected_idx]->lang_entry.name, app_metadata[selected_idx]->title_id + program_id_offset); if (!ncaInitializeContext(base_nca_ctx, user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \ - titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Program, program_id_offset), &base_tik)) + titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Program, program_id_offset), NULL)) { consolePrint("nca initialize base ctx failed\n"); goto out2; @@ -495,7 +494,7 @@ int main(int argc, char *argv[]) if (user_app_data.patch_info) { if (!ncaInitializeContext(update_nca_ctx, user_app_data.patch_info->storage_id, (user_app_data.patch_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \ - titleGetContentInfoByTypeAndIdOffset(user_app_data.patch_info, NcmContentType_Program, program_id_offset), &update_tik)) + titleGetContentInfoByTypeAndIdOffset(user_app_data.patch_info, NcmContentType_Program, program_id_offset), NULL)) { consolePrint("nca initialize update ctx failed\n"); goto out2; diff --git a/source/bfttf.c b/source/bfttf.c new file mode 100644 index 0000000..b4cde9a --- /dev/null +++ b/source/bfttf.c @@ -0,0 +1,230 @@ +/* + * bfttf.c + * + * Copyright (c) 2018, simontime. + * Copyright (c) 2020, DarkMatterCore . + * + * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). + * + * nxdumptool is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * nxdumptool is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "utils.h" +#include "bfttf.h" +#include "romfs.h" +#include "title.h" + +/* Type definitions. */ + +typedef struct { + u64 title_id; ///< System title ID. + char path[64]; ///< Path to BFTTF file inside the RomFS section from the system title. + u32 size; + u8 *data; +} BfttfFontInfo; + +/* Global variables. */ + +static Mutex g_bfttfMutex = 0; +static bool g_bfttfInterfaceInit = false; + +static BfttfFontInfo g_fontInfo[] = { + { 0x0100000000000811, "/nintendo_udsg-r_std_003.bfttf", 0, NULL }, /* FontStandard. */ + { 0x0100000000000810, "/nintendo_ext_003.bfttf", 0, NULL }, /* FontNintendoExtension (1).*/ + { 0x0100000000000810, "/nintendo_ext2_003.bfttf", 0, NULL }, /* FontNintendoExtension (2).*/ + { 0x0100000000000812, "/nintendo_udsg-r_ko_003.bfttf", 0, NULL }, /* FontKorean. */ + { 0x0100000000000814, "/nintendo_udsg-r_org_zh-cn_003.bfttf", 0, NULL }, /* FontChineseSimplified (1). */ + { 0x0100000000000814, "/nintendo_udsg-r_ext_zh-cn_003.bfttf", 0, NULL }, /* FontChineseSimplified (2). */ + { 0x0100000000000813, "/nintendo_udjxh-db_zh-tw_003.bfttf", 0, NULL } /* FontChineseTraditional. */ +}; + +static const u32 g_fontInfoCount = MAX_ELEMENTS(g_fontInfo); + +static const u32 g_bfttfKey = 0x06186249; + +/* Function prototypes. */ + +static bool bfttfDecodeFont(BfttfFontInfo *font_info); + +bool bfttfInitialize(void) +{ + mutexLock(&g_bfttfMutex); + + u32 count = 0; + NcaContext *nca_ctx = NULL; + TitleInfo *title_info = NULL; + u64 prev_title_id = 0; + + RomFileSystemContext romfs_ctx = {0}; + RomFileSystemFileEntry *romfs_file_entry = NULL; + + bool ret = g_bfttfInterfaceInit; + if (ret) goto end; + + /* Allocate memory for a temporary NCA context. */ + nca_ctx = calloc(1, sizeof(NcaContext)); + if (!nca_ctx) + { + LOGFILE("Failed to allocate memory for temporary NCA context!"); + goto end; + } + + /* Retrieve BFTTF data. */ + for(u32 i = 0; i < g_fontInfoCount; i++) + { + BfttfFontInfo *font_info = &(g_fontInfo[i]); + + /* Check if the title ID for the current font container matches the one from the previous font container. */ + /* We won't have to reinitialize both NCA and RomFS contexts if that's the case. */ + if (font_info->title_id != prev_title_id) + { + /* Get title info. */ + if (!(title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, font_info->title_id))) + { + LOGFILE("Failed to get title info for %016lX!", font_info->title_id); + continue; + } + + /* Initialize NCA context. */ + if (!ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), NULL)) + { + LOGFILE("Failed to initialize Data NCA context for %016lX!", font_info->title_id); + continue; + } + + /* Initialize RomFS context. */ + if (!romfsInitializeContext(&romfs_ctx, &(nca_ctx->fs_ctx[0]))) + { + LOGFILE("Failed to initialize RomFS context for Data NCA from %016lX!", font_info->title_id); + continue; + } + + /* Update previous title ID. */ + prev_title_id = font_info->title_id; + } + + /* Get RomFS file entry. */ + if (!(romfs_file_entry = romfsGetFileEntryByPath(&romfs_ctx, font_info->path))) + { + LOGFILE("Failed to retrieve RomFS file entry in %016lX!", font_info->title_id); + continue; + } + + /* Check file size. */ + if (!romfs_file_entry->size) + { + LOGFILE("Invalid file size for \"%s\" in %016lX!", font_info->path, font_info->title_id); + continue; + } + + /* Allocate memory for BFTTF data. */ + if (!(font_info->data = malloc(romfs_file_entry->size))) + { + LOGFILE("Failed to allocate 0x%lX bytes for \"%s\" in %016lX!", romfs_file_entry->size, font_info->path, font_info->title_id); + continue; + } + + /* Read BFTFF data. */ + if (!romfsReadFileEntryData(&romfs_ctx, romfs_file_entry, font_info->data, romfs_file_entry->size, 0)) + { + LOGFILE("Failed to read 0x%lX bytes long \"%s\" in %016lX!", romfs_file_entry->size, font_info->path, font_info->title_id); + free(font_info->data); + font_info->data = NULL; + continue; + } + + /* Update BFTTF size. */ + font_info->size = (u32)romfs_file_entry->size; + + /* Decode BFTTF data. */ + if (!bfttfDecodeFont(font_info)) + { + LOGFILE("Failed to decode 0x%lX bytes long \"%s\" in %016lX!", romfs_file_entry->size, font_info->path, font_info->title_id); + free(font_info->data); + font_info->data = NULL; + font_info->size = 0; + continue; + } + + /* Increase retrieved BFTTF count. */ + count++; + } + + ret = g_bfttfInterfaceInit = (count > 0); + if (!ret) LOGFILE("No BFTTF fonts retrieved!"); + +end: + romfsFreeContext(&romfs_ctx); + + if (nca_ctx) free(nca_ctx); + + mutexUnlock(&g_bfttfMutex); + + return ret; +} + +void bfttfExit(void) +{ + mutexLock(&g_bfttfMutex); + + /* Free BFTTF data. */ + for(u32 i = 0; i < g_fontInfoCount; i++) + { + BfttfFontInfo *font_info = &(g_fontInfo[i]); + font_info->size = 0; + if (font_info->data) free(font_info->data); + } + + g_bfttfInterfaceInit = false; + + mutexUnlock(&g_bfttfMutex); +} + +bool bfttfGetFontByType(BfttfFontData *font_data, u8 font_type) +{ + if (!font_data || font_type >= BfttfFontType_Total) + { + LOGFILE("Invalid parameters!"); + return false; + } + + BfttfFontInfo *font_info = &(g_fontInfo[font_type]); + if (font_info->size <= 8 || !font_info->data) + { + LOGFILE("BFTTF font data unavailable for type 0x%02X!", font_type); + return false; + } + + font_data->type = font_type; + font_data->size = (font_info->size - 8); + font_data->ptr = (font_info->data + 8); + + return true; +} + +static bool bfttfDecodeFont(BfttfFontInfo *font_info) +{ + if (!font_info || font_info->size <= 8 || !IS_ALIGNED(font_info->size, 4) || !font_info->data) + { + LOGFILE("Invalid parameters!"); + return false; + } + + for(u32 i = 8; i < (font_info->size - 8); i += 4) + { + u32 *ptr = (u32*)(font_info->data + i); + *ptr = (*ptr ^ g_bfttfKey); + } + + return true; +} diff --git a/source/bfttf.h b/source/bfttf.h new file mode 100644 index 0000000..5a78a5f --- /dev/null +++ b/source/bfttf.h @@ -0,0 +1,55 @@ +/* + * bftff.h + * + * Copyright (c) 2018, simontime. + * Copyright (c) 2020, DarkMatterCore . + * + * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). + * + * nxdumptool is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * nxdumptool is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#ifndef __BFTTF_H__ +#define __BFTTF_H__ + +/// Loosely based on PlSharedFontType. +typedef enum { + BfttfFontType_Standard = 0, ///< Japan, US and Europe + BfttfFontType_NintendoExt1 = 1, ///< Nintendo Extended (1). This font only has the special Nintendo-specific characters, which aren't available with the other fonts. + BfttfFontType_NintendoExt2 = 2, ///< Nintendo Extended (2). This font only has the special Nintendo-specific characters, which aren't available with the other fonts. + BfttfFontType_Korean = 3, ///< Korean (Hangul). + BfttfFontType_ChineseSimplified = 4, ///< Chinese Simplified. + BfttfFontType_ExtChineseSimplified = 5, ///< Extended Chinese Simplified. + BfttfFontType_ChineseTraditional = 6, ///< Chinese Traditional. + BfttfFontType_Total = 7 ///< Total fonts supported by this enum. +} BfttfFontType; + +/// Loosely based on PlFontData. +typedef struct { + u8 type; ///< BfttfFontType. + u32 size; ///< Decoded BFTFF font size. + u8 *ptr; ///< Pointer to font data. +} BfttfFontData; + +/// Initializes the BFTTF interface. +bool bfttfInitialize(void); + +/// Closes the BFTTF interface. +void bfttfExit(void); + +/// Returns a specific BFTTF font using the provided BfttfFontType. +bool bfttfGetFontByType(BfttfFontData *font, u8 font_type); + +#endif /* __BFTTF_H__ */ diff --git a/source/bktr.c b/source/bktr.c index 2b41e8b..9d64a06 100644 --- a/source/bktr.c +++ b/source/bktr.c @@ -50,6 +50,9 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct return false; } + /* Free output context beforehand. */ + bktrFreeContext(out); + /* Update missing base NCA RomFS status. */ out->missing_base_romfs = (!base_nca_fs_ctx->enabled || base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs || base_nca_fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx); diff --git a/source/nca.c b/source/nca.c index d64a473..be6bb20 100644 --- a/source/nca.c +++ b/source/nca.c @@ -88,7 +88,7 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, NcmContentStorage *ncm_storage = NULL; if (!out || (storage_id != NcmStorageId_GameCard && !(ncm_storage = titleGetNcmStorageByStorageId(storage_id))) || \ - (storage_id == NcmStorageId_GameCard && hfs_partition_type >= GameCardHashFileSystemPartitionType_Count) || !content_info || content_info->content_type > NcmContentType_DeltaFragment || !tik) + (storage_id == NcmStorageId_GameCard && hfs_partition_type >= GameCardHashFileSystemPartitionType_Count) || !content_info || content_info->content_type > NcmContentType_DeltaFragment) { LOGFILE("Invalid parameters!"); return false; @@ -138,12 +138,15 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, if (out->rights_id_available) { + Ticket tmp_tik = {0}; + Ticket *usable_tik = (tik ? tik : &tmp_tik); + /* Retrieve ticket. */ /* This will return true if it has already been retrieved. */ - if (tikRetrieveTicketByRightsId(tik, &(out->header.rights_id), out->storage_id == NcmStorageId_GameCard)) + if (tikRetrieveTicketByRightsId(usable_tik, &(out->header.rights_id), out->storage_id == NcmStorageId_GameCard)) { /* Copy decrypted titlekey. */ - memcpy(out->titlekey, tik->dec_titlekey, 0x10); + memcpy(out->titlekey, usable_tik->dec_titlekey, 0x10); out->titlekey_retrieved = true; } else { LOGFILE("Error retrieving ticket for NCA \"%s\"!", out->content_id_str); @@ -953,7 +956,12 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, } /* Clear output patch. */ - memset(out, 0, !is_integrity_patch ? sizeof(NcaHierarchicalSha256Patch) : sizeof(NcaHierarchicalIntegrityPatch)); + if (!is_integrity_patch) + { + ncaFreeHierarchicalSha256Patch(hierarchical_sha256_patch); + } else { + ncaFreeHierarchicalIntegrityPatch(hierarchical_integrity_patch); + } /* Process each layer. */ for(u32 i = layer_count; i > 0; i--) diff --git a/source/nca.h b/source/nca.h index de33424..82ff6bd 100644 --- a/source/nca.h +++ b/source/nca.h @@ -351,7 +351,9 @@ void ncaFreeCryptoBuffer(void); /// Initializes a NCA context. /// If 'storage_id' == NcmStorageId_GameCard, the 'hfs_partition_type' argument must be a valid GameCardHashFileSystemPartitionType value. -/// If the NCA holds a populated Rights ID field, and if the Ticket element pointed to by 'tik' hasn't been filled, ticket data will be retrieved. +/// If the NCA holds a populated Rights ID field, ticket data will need to be retrieved. +/// If the 'tik' argument points to a valid Ticket element, it will either be updated (if it's empty) or be used to read ticket data that has already been retrieved. +/// If the 'tik' argument is NULL, the function will just retrieve the necessary ticket data on its own. /// If ticket data can't be retrieved, the context will still be initialized, but anything that involves working with encrypted NCA FS section blocks won't be possible (e.g. ncaReadFsSection()). bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, const NcmContentInfo *content_info, Ticket *tik); diff --git a/source/pfs.c b/source/pfs.c index 33563ec..8856c66 100644 --- a/source/pfs.c +++ b/source/pfs.c @@ -39,8 +39,8 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext * u32 hash_region_count = 0; NcaRegion *hash_region = NULL; - /* Clear output Partition FS context. */ - memset(out, 0, sizeof(PartitionFileSystemContext)); + /* Free output context beforehand. */ + pfsFreeContext(out); /* Fill context. */ out->nca_fs_ctx = nca_fs_ctx; diff --git a/source/romfs.c b/source/romfs.c index fa862fe..2a90672 100644 --- a/source/romfs.c +++ b/source/romfs.c @@ -43,8 +43,8 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_ NcaRegion *hash_region = NULL; NcaHierarchicalIntegrityVerificationLevelInformation *level_information = NULL; - /* Clear output RomFS context. */ - memset(out, 0, sizeof(RomFileSystemContext)); + /* Free output context beforehand. */ + romfsFreeContext(out); /* Fill context. */ out->nca_fs_ctx = nca_fs_ctx; diff --git a/source/services.c b/source/services.c index 86881ab..45ff2df 100644 --- a/source/services.c +++ b/source/services.c @@ -41,7 +41,6 @@ typedef struct { static Result smHasService(bool *out_has_service, SmServiceName name); -static Result servicesPlUserInitialize(void); static Result servicesNifmUserInitialize(void); static bool servicesClkGetServiceType(void *arg); static bool servicesSplCryptoCheckAvailability(void *arg); @@ -56,7 +55,6 @@ static ServiceInfo g_serviceInfo[] = { { false, "spl:", NULL, &splInitialize, &splExit }, { false, "spl:mig", &servicesSplCryptoCheckAvailability, &splCryptoInitialize, &splCryptoExit }, /* Checks if spl:mig is really available (e.g. avoid calling splInitialize twice). */ { false, "pm:dmnt", NULL, &pmdmntInitialize, &pmdmntExit }, - { false, "pl:u", NULL, &servicesPlUserInitialize, &plExit }, { false, "psm", NULL, &psmInitialize, &psmExit }, { false, "nifm:u", NULL, &servicesNifmUserInitialize, &nifmExit }, { false, "clk", &servicesClkGetServiceType, NULL, NULL }, /* Placeholder for pcv / clkrst. */ @@ -219,11 +217,6 @@ static Result smHasService(bool *out_has_service, SmServiceName name) return rc; } -static Result servicesPlUserInitialize(void) -{ - return plInitialize(PlServiceType_User); -} - static Result servicesNifmUserInitialize(void) { return nifmInitialize(NifmServiceType_User); diff --git a/source/utils.c b/source/utils.c index 8034f0f..71e7acf 100644 --- a/source/utils.c +++ b/source/utils.c @@ -28,6 +28,7 @@ #include "nca.h" #include "usb.h" #include "title.h" +#include "bfttf.h" #include "fatfs/ff.h" #define LOGFILE_PATH "./" APP_TITLE ".log" @@ -125,6 +126,13 @@ bool utilsInitializeResources(void) goto end; } + /* Initialize BFTTF interface. */ + if (!bfttfInitialize()) + { + LOGFILE("Failed to initialize BFTTF interface!"); + goto end; + } + /* Retrieve pointer to the SD card FsFileSystem element. */ if (!(g_sdCardFileSystem = fsdevGetDeviceFileSystem("sdmc:"))) { @@ -186,6 +194,9 @@ void utilsCloseResources(void) /* Unmount eMMC BIS System partition. */ utilsUnmountEmmcBisSystemPartitionStorage(); + /* Deinitialize BFTTF interface. */ + bfttfExit(); + /* Deinitialize title interface. */ titleExit();