From 158e424b96ee96e087056780576d6f8cd753ad39 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Sun, 26 Jul 2020 00:57:12 -0400 Subject: [PATCH] List content infos as part of title list entries. Finally got rid of location resolver stuff. --- source/main.c | 257 +++------------------------------------------- source/nca.c | 11 +- source/nca.h | 15 +-- source/title.c | 269 +++++++++++++++++++++++++++++++++++++------------ source/title.h | 68 ++++++++++--- source/utils.c | 19 ++++ source/utils.h | 3 + todo.txt | 18 ++-- 8 files changed, 314 insertions(+), 346 deletions(-) diff --git a/source/main.c b/source/main.c index b48cb43..0a5653a 100644 --- a/source/main.c +++ b/source/main.c @@ -22,6 +22,7 @@ #include "bktr.h" #include "gamecard.h" #include "usb.h" +#include "title.h" #define TEST_BUF_SIZE 0x800000 @@ -212,41 +213,14 @@ int main(int argc, char *argv[]) goto out; } - - - - - goto out; - - - - - - - - - - - - - - - - - Result rc = 0; - u8 *buf = NULL; - u64 base_tid = (u64)0x010082400BCC6000; // ACNH 0x01006F8002326000 | Smash 0x01006A800016E000 | Dark Souls 0x01004AB00A260000 | BotW 0x01007EF00011E000 | Untitled Goose Game 0x010082400BCC6000 - u64 update_tid = (base_tid | 0x800); + u64 base_tid = (u64)0x01004AB00A260000; // ACNH 0x01006F8002326000 | Smash 0x01006A800016E000 | Dark Souls 0x01004AB00A260000 | BotW 0x01007EF00011E000 | Untitled Goose Game 0x010082400BCC6000 + u64 update_tid = titleGetPatchIdByApplicationId(base_tid); - Ticket base_tik = {0}, update_tik = {0}; + TitleInfo *base_title_info = NULL, *update_title_info = NULL; NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL; - NcmContentStorage ncm_storage_sdcard = {0}, ncm_storage_emmc = {0}; - - char path[FS_MAX_PATH] = {0}; - LrLocationResolver resolver_sdcard = {0}, resolver_emmc = {0}; - NcmContentInfo content_info = {0}; + Ticket base_tik = {0}, update_tik = {0}; BktrContext bktr_ctx = {0}; @@ -280,110 +254,29 @@ int main(int argc, char *argv[]) consolePrint("update nca ctx buf succeeded\n"); - rc = ncmOpenContentStorage(&ncm_storage_sdcard, NcmStorageId_SdCard); - if (R_FAILED(rc)) + base_title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_Any, base_tid); + update_title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_Any, update_tid); + + if (!base_title_info || !update_title_info) { - consolePrint("ncm open storage sdcard failed\n"); + consolePrint("title info failed\n"); goto out2; } - consolePrint("ncm open storage sdcard succeeded\n"); + consolePrint("title info succeeded\n"); - rc = ncmOpenContentStorage(&ncm_storage_emmc, NcmStorageId_BuiltInUser); - if (R_FAILED(rc)) + if (!ncaInitializeContext(base_nca_ctx, base_title_info->storage_id, 0, titleGetContentInfoByTypeAndIdOffset(base_title_info, NcmContentType_Program, 0), &base_tik)) { - consolePrint("ncm open storage emmc failed\n"); + consolePrint("nca initialize base ctx failed\n"); goto out2; } - consolePrint("ncm open storage emmc succeeded\n"); - - rc = lrInitialize(); - if (R_FAILED(rc)) + if (!ncaInitializeContext(update_nca_ctx, update_title_info->storage_id, 0, titleGetContentInfoByTypeAndIdOffset(update_title_info, NcmContentType_Program, 0), &update_tik)) { - consolePrint("lrInitialize failed\n"); + consolePrint("nca initialize update ctx failed\n"); goto out2; } - consolePrint("lrInitialize succeeded\n"); - - rc = lrOpenLocationResolver(NcmStorageId_SdCard, &resolver_sdcard); - if (R_FAILED(rc)) - { - consolePrint("lrOpenLocationResolver sdcard failed\n"); - goto out2; - } - - consolePrint("lrOpenLocationResolver sdcard succeeded\n"); - - rc = lrOpenLocationResolver(NcmStorageId_BuiltInUser, &resolver_emmc); - if (R_FAILED(rc)) - { - consolePrint("lrOpenLocationResolver emmc failed\n"); - goto out2; - } - - consolePrint("lrOpenLocationResolver emmc succeeded\n"); - - for(u32 i = 0; i < 2; i++) - { - for(u32 j = 0; j < 2; j++) - { - NcmContentStorage *ncm_storage = (j == 0 ? &ncm_storage_sdcard : &ncm_storage_emmc); - LrLocationResolver *resolver = (j == 0 ? &resolver_sdcard : &resolver_emmc); - NcaContext *nca_ctx = (i == 0 ? base_nca_ctx : update_nca_ctx); - Ticket *tik = (i == 0 ? &base_tik : &update_tik); - - rc = lrLrResolveProgramPath(resolver, i == 0 ? base_tid : update_tid, path); - if (R_FAILED(rc)) - { - consolePrint("lrLrResolveProgramPath %s,%s failed\n", i == 0 ? "base" : "update", j == 0 ? "sdcard" : "emmc"); - if (j == 0) continue; - goto out2; - } - - consolePrint("lrLrResolveProgramPath %s,%s succeeded\n", i == 0 ? "base" : "update", j == 0 ? "sdcard" : "emmc"); - - memset(&content_info, 0, sizeof(NcmContentInfo)); - - memmove(path, strrchr(path, '/') + 1, SHA256_HASH_SIZE + 4); - path[SHA256_HASH_SIZE + 4] = '\0'; - - consolePrint("Program NCA (%s,%s): %s\n", i == 0 ? "base" : "update", j == 0 ? "sdcard" : "emmc", path); - - for(u32 i = 0; i < SHA256_HASH_SIZE; i++) - { - char val = (('a' <= path[i] && path[i] <= 'f') ? (path[i] - 'a' + 0xA) : (path[i] - '0')); - if ((i & 1) == 0) val <<= 4; - content_info.content_id.c[i >> 1] |= val; - } - - content_info.content_type = NcmContentType_Program; - - u64 content_size = 0; - rc = ncmContentStorageGetSizeFromContentId(ncm_storage, (s64*)&content_size, &(content_info.content_id)); - if (R_FAILED(rc)) - { - consolePrint("ncmContentStorageGetSizeFromContentId %s,%s failed\n", i == 0 ? "base" : "update", j == 0 ? "sdcard" : "emmc"); - goto out2; - } - - consolePrint("ncmContentStorageGetSizeFromContentId %s,%s succeeded\n", i == 0 ? "base" : "update", j == 0 ? "sdcard" : "emmc"); - - memcpy(&(content_info.size), &content_size, 6); - - if (!ncaInitializeContext(nca_ctx, i == 0 ? NcmStorageId_SdCard : NcmStorageId_BuiltInUser, ncm_storage, 0, &content_info, tik)) - { - consolePrint("nca initialize ctx %s,%s failed\n", i == 0 ? "base" : "update", j == 0 ? "sdcard" : "emmc"); - goto out2; - } - - consolePrint("nca initialize ctx %s,%s succeeded\n", i == 0 ? "base" : "update", j == 0 ? "sdcard" : "emmc"); - - break; - } - } - if (!bktrInitializeContext(&bktr_ctx, &(base_nca_ctx->fs_contexts[1]), &(update_nca_ctx->fs_contexts[1]))) { consolePrint("bktr initialize ctx failed\n"); @@ -392,117 +285,6 @@ int main(int argc, char *argv[]) consolePrint("bktr initialize ctx succeeded\n"); - - - - - - - - - - - - - - - - - FILE *tmp_file = NULL; - RomFileSystemFileEntry *romfs_file_entry = NULL; - RomFileSystemFileEntryPatch romfs_patch = {0}; - - romfs_file_entry = romfsGetFileEntryByPath(&(bktr_ctx.base_romfs_ctx), "/Data/rawsettings"); - if (!romfs_file_entry) - { - consolePrint("romfs get file entry by path failed\n"); - goto out2; - } - - consolePrint("romfs get file entry by path success: %s | 0x%lX | %p\n", romfs_file_entry->name, romfs_file_entry->size, romfs_file_entry); - - if (!romfsReadFileEntryData(&(bktr_ctx.base_romfs_ctx), romfs_file_entry, buf, romfs_file_entry->size, 0)) - { - consolePrint("romfs read file entry failed\n"); - goto out2; - } - - consolePrint("romfs read file entry success\n"); - - memset(buf, 0xAA, romfs_file_entry->size); - - if (!romfsGenerateFileEntryPatch(&(bktr_ctx.base_romfs_ctx), romfs_file_entry, buf, romfs_file_entry->size, 0, &romfs_patch)) - { - consolePrint("romfs file entry patch failed\n"); - goto out2; - } - - consolePrint("romfs file entry patch success\n"); - - if (!ncaEncryptHeader(base_nca_ctx)) - { - consolePrint("nca header mod not encrypted\n"); - romfsFreeFileEntryPatch(&romfs_patch); - goto out2; - } - - consolePrint("nca header mod encrypted\n"); - - tmp_file = fopen("sdmc:/program_nca_mod.bin", "wb"); - if (!tmp_file) - { - consolePrint("program nca mod not saved\n"); - romfsFreeFileEntryPatch(&romfs_patch); - goto out2; - } - - u64 block_size = TEST_BUF_SIZE; - for(u64 i = 0; i < base_nca_ctx->content_size; i += block_size) - { - if (block_size > (base_nca_ctx->content_size - i)) block_size = (base_nca_ctx->content_size - i); - - if (!ncaReadContentFile(base_nca_ctx, buf, block_size, i)) - { - consolePrint("failed to read 0x%lX chunk from offset 0x%lX\n", block_size, i); - fclose(tmp_file); - romfsFreeFileEntryPatch(&romfs_patch); - goto out2; - } - - if (i == 0) - { - memcpy(buf, &(base_nca_ctx->header), sizeof(NcaHeader)); - for(u64 j = 0; j < 4; j++) memcpy(buf + sizeof(NcaHeader) + (j * sizeof(NcaFsHeader)), &(base_nca_ctx->fs_contexts[j].header), sizeof(NcaFsHeader)); - } - - romfsWriteFileEntryPatchToMemoryBuffer(&(bktr_ctx.base_romfs_ctx), &romfs_patch, buf, block_size, i); - - fwrite(buf, 1, block_size, tmp_file); - fflush(tmp_file); - - consolePrint("wrote 0x%lX bytes to offset 0x%lX\n", block_size, i); - } - - fclose(tmp_file); - romfsFreeFileEntryPatch(&romfs_patch); - - goto out2; - - - - - - - - - - - - - - - - shared_data.bktr_ctx = &bktr_ctx; shared_data.data = buf; shared_data.data_size = 0; @@ -616,15 +398,6 @@ out2: bktrFreeContext(&bktr_ctx); - if (serviceIsActive(&(resolver_emmc.s))) serviceClose(&(resolver_emmc.s)); - if (serviceIsActive(&(resolver_sdcard.s))) serviceClose(&(resolver_sdcard.s)); - - lrExit(); - - if (serviceIsActive(&(ncm_storage_emmc.s))) ncmContentStorageClose(&ncm_storage_emmc); - - if (serviceIsActive(&(ncm_storage_sdcard.s))) ncmContentStorageClose(&ncm_storage_sdcard); - if (update_nca_ctx) free(update_nca_ctx); if (base_nca_ctx) free(base_nca_ctx); diff --git a/source/nca.c b/source/nca.c index f4ee7fc..cd639cb 100644 --- a/source/nca.c +++ b/source/nca.c @@ -24,6 +24,7 @@ #include "aes.h" #include "rsa.h" #include "gamecard.h" +#include "title.h" #define NCA_CRYPTO_BUFFER_SIZE 0x800000 /* 8 MiB. */ @@ -82,10 +83,12 @@ void ncaFreeCryptoBuffer(void) mutexUnlock(&g_ncaCryptoBufferMutex); } -bool ncaInitializeContext(NcaContext *out, u8 storage_id, NcmContentStorage *ncm_storage, u8 hfs_partition_type, const NcmContentInfo *content_info, Ticket *tik) +bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, const NcmContentInfo *content_info, Ticket *tik) { - if (!out || !tik || (storage_id != NcmStorageId_GameCard && !ncm_storage) || (storage_id == NcmStorageId_GameCard && hfs_partition_type > GameCardHashFileSystemPartitionType_Secure) || \ - !content_info || content_info->content_type > NcmContentType_DeltaFragment) + NcmContentStorage *ncm_storage = NULL; + + if (!out || (storage_id != NcmStorageId_GameCard && !(ncm_storage = titleGetNcmStorageByStorageId(storage_id))) || \ + (storage_id == NcmStorageId_GameCard && hfs_partition_type > GameCardHashFileSystemPartitionType_Boot) || !content_info || content_info->content_type > NcmContentType_DeltaFragment || !tik) { LOGFILE("Invalid parameters!"); return false; @@ -104,7 +107,7 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, NcmContentStorage *ncm out->content_type = content_info->content_type; out->id_offset = content_info->id_offset; - ncaConvertNcmContentSizeToU64(content_info->size, &(out->content_size)); + titleConvertNcmContentSizeToU64(content_info->size, &(out->content_size)); if (out->content_size < NCA_FULL_HEADER_LENGTH) { LOGFILE("Invalid size for NCA \"%s\"!", out->content_id_str); diff --git a/source/nca.h b/source/nca.h index 7ccccdd..de31abe 100644 --- a/source/nca.h +++ b/source/nca.h @@ -348,11 +348,10 @@ bool ncaAllocateCryptoBuffer(void); void ncaFreeCryptoBuffer(void); /// Initializes a NCA context. -/// If 'storage_id' != NcmStorageId_GameCard, the 'ncm_storage' argument must point to a valid NcmContentStorage instance, previously opened using the same NcmStorageId value. /// 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 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, NcmContentStorage *ncm_storage, u8 hfs_partition_type, const NcmContentInfo *content_info, Ticket *tik); +bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, const NcmContentInfo *content_info, Ticket *tik); /// Reads raw encrypted data from a NCA using an input context, previously initialized by ncaInitializeContext(). /// Input offset must be relative to the start of the NCA content file. @@ -428,18 +427,6 @@ bool ncaEncryptHeader(NcaContext *ctx); /// Miscellaneous functions. -NX_INLINE void ncaConvertNcmContentSizeToU64(const u8 *size, u64 *out) -{ - if (!size || !out) return; - *out = 0; - memcpy(out, size, 6); -} - -NX_INLINE void ncaConvertU64ToNcmContentSize(const u64 *size, u8 *out) -{ - if (size && out) memcpy(out, size, 6); -} - NX_INLINE void ncaSetDownloadDistributionType(NcaContext *ctx) { if (!ctx || ctx->header.distribution_type == NcaDistributionType_Download) return; diff --git a/source/title.c b/source/title.c index e0df19b..c4b8e07 100644 --- a/source/title.c +++ b/source/title.c @@ -42,6 +42,11 @@ static u32 g_titleInfoCount = 0, g_titleInfoGameCardStartIndex = 0, g_titleInfoG /* Function prototypes. */ +NX_INLINE void titleFreeApplicationMetadata(void); +NX_INLINE void titleFreeTitleInfo(void); + +NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id); + static bool titleRetrieveApplicationMetadataFromNsRecords(void); static bool titleRetrieveApplicationMetadataByTitleId(u64 title_id, TitleApplicationMetadata *out); @@ -55,13 +60,12 @@ static bool titleOpenNcmDatabaseAndStorageFromGameCard(void); static void titleCloseNcmDatabaseAndStorageFromGameCard(void); static bool titleLoadTitleInfo(void); -static bool titleRetrieveContentMetaKeysFromDatabase(u8 storage_id, NcmContentMetaDatabase *ncm_db); +static bool titleRetrieveContentMetaKeysFromDatabase(u8 storage_id); +static bool titleGetContentInfosFromTitle(u8 storage_id, const NcmContentMetaKey *meta_key, NcmContentInfo **out_content_infos, u32 *out_content_count); static bool _titleRefreshGameCardTitleInfo(bool lock); static void titleRemoveGameCardTitleInfoEntries(void); -NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id); - bool titleInitialize(void) { mutexLock(&g_titleMutex); @@ -70,7 +74,7 @@ bool titleInitialize(void) if (ret) goto end; /* Allocate memory for the ns application control data. */ - /* This will be used each time we need to retrieve application metadata. */ + /* This will be used each time we need to retrieve the metadata from an application. */ g_nsAppControlData = calloc(1, sizeof(NsApplicationControlData)); if (!g_nsAppControlData) { @@ -120,7 +124,6 @@ bool titleInitialize(void) - if (g_titleInfo && g_titleInfoCount) { mkdir("sdmc:/records", 0777); @@ -133,16 +136,33 @@ bool titleInitialize(void) { for(u32 i = 0; i < g_titleInfoCount; i++) { - TitleVersion version; - memcpy(&version, &(g_titleInfo[i].meta_key.version), sizeof(u32)); - fprintf(title_infos_txt, "Storage ID: 0x%02X\r\n", g_titleInfo[i].storage_id); fprintf(title_infos_txt, "Title ID: %016lX\r\n", g_titleInfo[i].meta_key.id); - fprintf(title_infos_txt, "Version: %u (%u.%u.%u-%u.%u)\r\n", g_titleInfo[i].meta_key.version, version.TitleVersion_Major, version.TitleVersion_Minor, version.TitleVersion_Micro, \ - version.TitleVersion_MajorRelstep, version.TitleVersion_MinorRelstep); + fprintf(title_infos_txt, "Version: %u (%u.%u.%u-%u.%u)\r\n", g_titleInfo[i].meta_key.version, g_titleInfo[i].dot_version.TitleVersion_Major, \ + g_titleInfo[i].dot_version.TitleVersion_Minor, g_titleInfo[i].dot_version.TitleVersion_Micro, g_titleInfo[i].dot_version.TitleVersion_MajorRelstep, \ + g_titleInfo[i].dot_version.TitleVersion_MinorRelstep); fprintf(title_infos_txt, "Type: 0x%02X\r\n", g_titleInfo[i].meta_key.type); fprintf(title_infos_txt, "Install Type: 0x%02X\r\n", g_titleInfo[i].meta_key.install_type); - fprintf(title_infos_txt, "Title Size: 0x%lX\r\n", g_titleInfo[i].title_size); + fprintf(title_infos_txt, "Title Size: %s (0x%lX)\r\n", g_titleInfo[i].title_size_str, g_titleInfo[i].title_size); + + fprintf(title_infos_txt, "Content Count: %u\r\n", g_titleInfo[i].content_count); + for(u32 j = 0; j < g_titleInfo[i].content_count; j++) + { + char content_id_str[SHA256_HASH_SIZE + 1] = {0}; + utilsGenerateHexStringFromData(content_id_str, sizeof(content_id_str), g_titleInfo[i].content_infos[j].content_id.c, SHA256_HASH_SIZE / 2); + + u64 content_size = 0; + titleConvertNcmContentSizeToU64(g_titleInfo[i].content_infos[j].size, &content_size); + + char content_size_str[32] = {0}; + utilsGenerateFormattedSizeString(content_size, content_size_str, sizeof(content_size_str)); + + fprintf(title_infos_txt, " Content #%u:\r\n", j + 1); + fprintf(title_infos_txt, " Content ID: %s\r\n", content_id_str); + fprintf(title_infos_txt, " Content Size: %s (0x%lX)\r\n", content_size_str, content_size); + fprintf(title_infos_txt, " Content Type: 0x%02X\r\n", g_titleInfo[i].content_infos[j].content_type); + fprintf(title_infos_txt, " ID Offset: 0x%02X\r\n", g_titleInfo[i].content_infos[j].id_offset); + } if (g_titleInfo[i].app_metadata) { @@ -196,26 +216,12 @@ void titleExit(void) { mutexLock(&g_titleMutex); - - - - - - - - - - - - + /* Free title info. */ + titleFreeTitleInfo(); /* Close gamecard ncm database and storage. */ titleCloseNcmDatabaseAndStorageFromGameCard(); - /* Free title info. */ - if (g_titleInfo) free(g_titleInfo); - g_titleInfoCount = g_titleInfoGameCardStartIndex = g_titleInfoGameCardCount = 0; - /* Close eMMC System, eMMC User and SD card ncm storages. */ titleCloseNcmStorages(); @@ -223,8 +229,7 @@ void titleExit(void) titleCloseNcmDatabases(); /* Free application metadata. */ - if (g_appMetadata) free(g_appMetadata); - g_appMetadataCount = 0; + titleFreeApplicationMetadata(); /* Free ns application control data. */ if (g_nsAppControlData) free(g_nsAppControlData); @@ -289,6 +294,34 @@ bool titleRefreshGameCardTitleInfo(void) return _titleRefreshGameCardTitleInfo(true); } +TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id) +{ + mutexLock(&g_titleMutex); + + TitleInfo *info = NULL; + + if (!g_titleInfo || !g_titleInfoCount || storage_id < NcmStorageId_GameCard || storage_id > NcmStorageId_Any || !title_id) + { + LOGFILE("Invalid parameters!"); + goto end; + } + + for(u32 i = 0; i < g_titleInfoCount; i++) + { + if (g_titleInfo[i].meta_key.id == title_id && (storage_id == NcmStorageId_Any || (storage_id != NcmStorageId_Any && g_titleInfo[i].storage_id == storage_id))) + { + info = &(g_titleInfo[i]); + break; + } + } + + if (!info) LOGFILE("Unable to find TitleInfo entry with ID \"%016lX\"! (storage ID %u).", title_id, storage_id); + +end: + mutexUnlock(&g_titleMutex); + + return info; +} @@ -304,9 +337,44 @@ bool titleRefreshGameCardTitleInfo(void) +NX_INLINE void titleFreeApplicationMetadata(void) +{ + if (g_appMetadata) + { + free(g_appMetadata); + g_appMetadata = NULL; + } + + g_appMetadataCount = 0; +} +NX_INLINE void titleFreeTitleInfo(void) +{ + if (g_titleInfo) + { + for(u32 i = 0; i < g_titleInfoCount; i++) + { + if (g_titleInfo[i].content_infos) free(g_titleInfo[i].content_infos); + } + + free(g_titleInfo); + g_titleInfo = NULL; + } + + g_titleInfoCount = g_titleInfoGameCardStartIndex = g_titleInfoGameCardCount = 0; +} - +NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id) +{ + if (!g_appMetadata || !g_appMetadataCount || !title_id) return NULL; + + for(u32 i = 0; i < g_appMetadataCount; i++) + { + if (g_appMetadata[i].title_id == title_id) return &(g_appMetadata[i]); + } + + return NULL; +} static bool titleRetrieveApplicationMetadataFromNsRecords(void) { @@ -585,21 +653,12 @@ static bool titleLoadTitleInfo(void) /* Return right away if title info has already been retrieved. */ if (g_titleInfo || g_titleInfoCount) return true; - NcmContentMetaDatabase *ncm_db = NULL; - g_titleInfoCount = 0; for(u8 i = NcmStorageId_BuiltInSystem; i <= NcmStorageId_SdCard; i++) { - /* Retrieve ncm database pointer. */ - ncm_db = titleGetNcmDatabaseByStorageId(i); - if (!ncm_db) continue; - - /* Check if the ncm database handle has already been retrieved. */ - if (!serviceIsActive(&(ncm_db->s))) continue; - - /* Retrieve content meta keys from this ncm database. */ - if (!titleRetrieveContentMetaKeysFromDatabase(i, ncm_db)) + /* Retrieve content meta keys from the current storage. */ + if (!titleRetrieveContentMetaKeysFromDatabase(i)) { LOGFILE("Failed to retrieve content meta keys from storage ID %u!", i); return false; @@ -609,9 +668,11 @@ static bool titleLoadTitleInfo(void) return true; } -static bool titleRetrieveContentMetaKeysFromDatabase(u8 storage_id, NcmContentMetaDatabase *ncm_db) +static bool titleRetrieveContentMetaKeysFromDatabase(u8 storage_id) { - if (storage_id < NcmStorageId_GameCard || storage_id > NcmStorageId_SdCard || !ncm_db || !serviceIsActive(&(ncm_db->s))) + NcmContentMetaDatabase *ncm_db = NULL; + + if (!(ncm_db = titleGetNcmDatabaseByStorageId(storage_id)) || !serviceIsActive(&(ncm_db->s))) { LOGFILE("Invalid parameters!"); return false; @@ -705,10 +766,26 @@ static bool titleRetrieveContentMetaKeysFromDatabase(u8 storage_id, NcmContentMe { TitleInfo *cur_title_info = &(g_titleInfo[g_titleInfoCount + i]); + /* Fill information. */ cur_title_info->storage_id = storage_id; + memcpy(&(cur_title_info->dot_version), &(meta_keys[i].version), sizeof(u32)); memcpy(&(cur_title_info->meta_key), &(meta_keys[i]), sizeof(NcmContentMetaKey)); - /* TO DO: RETRIEVE TITLE SIZE HERE. */ cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(meta_keys[i].id); + + /* Retrieve content infos. */ + if (titleGetContentInfosFromTitle(storage_id, &(meta_keys[i]), &(cur_title_info->content_infos), &(cur_title_info->content_count))) + { + /* Calculate title size. */ + for(u32 j = 0; j < cur_title_info->content_count; j++) + { + u64 tmp_size = 0; + titleConvertNcmContentSizeToU64(cur_title_info->content_infos[j].size, &tmp_size); + cur_title_info->title_size += tmp_size; + } + } + + /* Generate formatted title size string. */ + utilsGenerateFormattedSizeString(cur_title_info->title_size, cur_title_info->title_size_str, sizeof(cur_title_info->title_size_str)); } /* Update title info count. */ @@ -722,6 +799,82 @@ end: return success; } +static bool titleGetContentInfosFromTitle(u8 storage_id, const NcmContentMetaKey *meta_key, NcmContentInfo **out_content_infos, u32 *out_content_count) +{ + NcmContentMetaDatabase *ncm_db = NULL; + + if (!(ncm_db = titleGetNcmDatabaseByStorageId(storage_id)) || !serviceIsActive(&(ncm_db->s)) || !meta_key || !out_content_infos || !out_content_count) + { + LOGFILE("Invalid parameters!"); + return false; + } + + Result rc = 0; + + NcmContentMetaHeader content_meta_header = {0}; + u64 content_meta_header_read_size = 0; + + NcmContentInfo *content_infos = NULL; + u32 content_count = 0, written = 0; + + bool success = false; + + /* Retrieve content meta header. */ + rc = ncmContentMetaDatabaseGet(ncm_db, meta_key, &content_meta_header_read_size, &content_meta_header, sizeof(NcmContentMetaHeader)); + if (R_FAILED(rc)) + { + LOGFILE("ncmContentMetaDatabaseGet failed! (0x%08X).", rc); + goto end; + } + + if (content_meta_header_read_size != sizeof(NcmContentMetaHeader)) + { + LOGFILE("Content meta header size mismatch! (0x%lX != 0x%lX).", rc, content_meta_header_read_size, sizeof(NcmContentMetaHeader)); + goto end; + } + + /* Get content count. */ + content_count = (u32)content_meta_header.content_count; + if (!content_count) + { + LOGFILE("Content count is zero!"); + goto end; + } + + /* Allocate memory for the content infos. */ + content_infos = calloc(content_count, sizeof(NcmContentInfo)); + if (!content_infos) + { + LOGFILE("Unable to allocate memory for the content infos buffer! (%u content[s]).", content_count); + goto end; + } + + /* Retrieve content infos. */ + rc = ncmContentMetaDatabaseListContentInfo(ncm_db, (s32*)&written, content_infos, (s32)content_count, meta_key, 0); + if (R_FAILED(rc)) + { + LOGFILE("ncmContentMetaDatabaseListContentInfo failed! (0x%08X).", rc); + goto end; + } + + if (written != content_count) + { + LOGFILE("Content count mismatch! (%u != %u).", written, content_count); + goto end; + } + + /* Update output. */ + *out_content_infos = content_infos; + *out_content_count = content_count; + + success = true; + +end: + if (!success && content_infos) free(content_infos); + + return success; +} + static bool _titleRefreshGameCardTitleInfo(bool lock) { if (lock) mutexLock(&g_titleMutex); @@ -749,7 +902,7 @@ static bool _titleRefreshGameCardTitleInfo(bool lock) g_titleInfoGameCardStartIndex = g_titleInfoCount; /* Retrieve content meta keys from the gamecard ncm database. */ - if (!titleRetrieveContentMetaKeysFromDatabase(NcmStorageId_GameCard, &g_ncmDbGameCard)) + if (!titleRetrieveContentMetaKeysFromDatabase(NcmStorageId_GameCard)) { LOGFILE("Failed to retrieve content meta keys from gamecard!"); goto end; @@ -856,29 +1009,21 @@ static void titleRemoveGameCardTitleInfoEntries(void) if (g_titleInfoGameCardCount == g_titleInfoCount) { - free(g_titleInfo); - g_titleInfo = NULL; + titleFreeTitleInfo(); } else { + for(u32 i = (g_titleInfoCount - g_titleInfoGameCardCount); i < g_titleInfoCount; i++) + { + if (g_titleInfo[i].content_infos) free(g_titleInfo[i].content_infos); + } + TitleInfo *tmp_title_info = realloc(g_titleInfo, (g_titleInfoCount - g_titleInfoGameCardCount) * sizeof(TitleInfo)); if (tmp_title_info) { g_titleInfo = tmp_title_info; tmp_title_info = NULL; } + + g_titleInfoCount = (g_titleInfoCount - g_titleInfoGameCardCount); + g_titleInfoGameCardStartIndex = g_titleInfoGameCardCount = 0; } - - g_titleInfoCount = (g_titleInfoCount - g_titleInfoGameCardCount); - g_titleInfoGameCardStartIndex = g_titleInfoGameCardCount = 0; -} - -NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id) -{ - if (!g_appMetadata || !g_appMetadataCount || !title_id) return NULL; - - for(u32 i = 0; i < g_appMetadataCount; i++) - { - if (g_appMetadata[i].title_id == title_id) return &(g_appMetadata[i]); - } - - return NULL; } diff --git a/source/title.h b/source/title.h index fea7b36..9942ccf 100644 --- a/source/title.h +++ b/source/title.h @@ -34,44 +34,76 @@ typedef struct { u32 TitleVersion_Major : 6; } TitleVersion; -/// Retrieved from ns application records. +/// Retrieved using ns application records and/or ncm content meta keys. typedef struct { u64 title_id; ///< Title ID from the application this data belongs to. - NacpLanguageEntry lang_entry; ///< UTF-8 strings in the language set in the console settings. + NacpLanguageEntry lang_entry; ///< UTF-8 strings in the console language. u32 icon_size; ///< JPEG icon size. u8 icon[0x20000]; ///< JPEG icon data. } TitleApplicationMetadata; -/// Retrieved from ncm databases. +/// Retrieved using ncm databases. typedef struct { u8 storage_id; ///< NcmStorageId. + TitleVersion dot_version; ///< Holds the same value from meta_key.version. Used to display version numbers in dot notation (major.minor.micro-major_relstep.minor_relstep). NcmContentMetaKey meta_key; ///< Used with ncm calls. + u32 content_count; ///< Content info count. + NcmContentInfo *content_infos; ///< Content info entries from this title. u64 title_size; ///< Total title size. - TitleApplicationMetadata *app_metadata; ///< Not available for all titles. + char title_size_str[32]; ///< Total title size string. + TitleApplicationMetadata *app_metadata; ///< Only available for applications. + + + /* Pointers to patches / AOC? */ + + } TitleInfo; - - - - - - - - +/// Initializes the title interface. bool titleInitialize(void); + +/// Closes the title interface. void titleExit(void); +/// Returns a pointer to a ncm database handle using a NcmStorageId value. NcmContentMetaDatabase *titleGetNcmDatabaseByStorageId(u8 storage_id); + +/// Returns a pointer to a ncm storage handle using a NcmStorageId value. NcmContentStorage *titleGetNcmStorageByStorageId(u8 storage_id); /// Returns true if gamecard title info could be loaded. /// Suitable for being called between UI updates. bool titleRefreshGameCardTitleInfo(void); +/// Retrieves a pointer to a TitleInfo entry with a matching storage ID and title ID. +/// If NcmStorageId_Any is used, the first entry with a matching title ID is returned. +/// Returns NULL if an error occurs. +TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id); + + + + + + + + + /// Miscellaneous functions. +NX_INLINE void titleConvertNcmContentSizeToU64(const u8 *size, u64 *out) +{ + if (!size || !out) return; + *out = 0; + memcpy(out, size, 6); +} + +NX_INLINE void titleConvertU64ToNcmContentSize(const u64 *size, u8 *out) +{ + if (size && out) memcpy(out, size, 6); +} + NX_INLINE u64 titleGetPatchIdByApplicationId(u64 app_id) { return (app_id | TITLE_PATCH_ID_MASK); @@ -92,4 +124,16 @@ NX_INLINE bool titleCheckIfAddOnContentIdBelongsToApplicationId(u64 app_id, u64 return ((app_id & TITLE_ADDONCONTENT_ID_MASK) == (aoc_id & TITLE_ADDONCONTENT_ID_MASK)); } +NX_INLINE NcmContentInfo *titleGetContentInfoByTypeAndIdOffset(TitleInfo *info, u8 content_type, u8 id_offset) +{ + if (!info || !info->content_count || !info->content_infos || content_type > NcmContentType_DeltaFragment) return NULL; + + for(u32 i = 0; i < info->content_count; i++) + { + if (info->content_infos[i].content_type == content_type && info->content_infos[i].id_offset == id_offset) return &(info->content_infos[i]); + } + + return NULL; +} + #endif /* __TITLE_H__ */ diff --git a/source/utils.c b/source/utils.c index 30fe29d..f8263b9 100644 --- a/source/utils.c +++ b/source/utils.c @@ -52,6 +52,9 @@ static AppletHookCookie g_systemOverclockCookie = {0}; static Mutex g_logfileMutex = 0; +static const char *g_sizeSuffixes[] = { "B", "KiB", "MiB", "GiB" }; +static const u32 g_sizeSuffixesCount = MAX_ELEMENTS(g_sizeSuffixes); + /* Function prototypes. */ static u64 utilsHidKeysAllDown(void); @@ -352,6 +355,22 @@ void utilsGenerateHexStringFromData(char *dst, size_t dst_size, const void *src, dst[j] = '\0'; } +void utilsGenerateFormattedSizeString(u64 size, char *dst, size_t dst_size) +{ + if (!dst || dst_size < 2) return; + + double converted_size = (double)size; + + for(u32 i = 0; i < g_sizeSuffixesCount; i++) + { + if (converted_size >= pow(1024.0, i + 1) && (i + 1) < g_sizeSuffixesCount) continue; + + converted_size /= pow(1024.0, i); + snprintf(dst, dst_size, "%.*f %s", (converted_size >= 100.0 ? 0 : (converted_size >= 10.0 ? 1 : 2)), converted_size, g_sizeSuffixes[i]); + break; + } +} + bool utilsGetFreeSdCardSpace(u64 *out) { return utilsGetFreeFileSystemSpace(g_sdCardFileSystem, out); diff --git a/source/utils.h b/source/utils.h index 0762b63..ac106ef 100644 --- a/source/utils.h +++ b/source/utils.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -95,6 +96,8 @@ void utilsTrimString(char *str); void utilsGenerateHexStringFromData(char *dst, size_t dst_size, const void *src, size_t src_size); +void utilsGenerateFormattedSizeString(u64 size, char *dst, size_t dst_size); + bool utilsGetFreeSdCardSpace(u64 *out); bool utilsGetFreeFileSystemSpace(FsFileSystem *fs, u64 *out); diff --git a/todo.txt b/todo.txt index 558a624..0d6172b 100644 --- a/todo.txt +++ b/todo.txt @@ -1,5 +1,6 @@ todo: + nca: functions for fs section lookup? (could just let the user choose...) nca: function to write re-encrypted nca headers / nca fs headers (don't forget nca0 please) nca: function to patch the private npdm acid signature from a program nca + patch the acid signature from the nca header @@ -18,17 +19,10 @@ todo: bktr: filelist generation functions (wrappers for romfs filelist generation functions) + title: linked lists for patch / aoc info? + title: hardcode names for system titles + title: more functions for title lookup (filters, patches / aoc, etc.) + title: more functions for content lookup (based on id?) + title: find a nice way to deal with orphan content - - - - char content_info_path[FS_MAX_PATH] = {0}; - sprintf(content_info_path, "sdmc:/%016lX.bin", xml_program_info.title_id); - - FILE *content_info = fopen(content_info_path, "wb"); - if (content_info) - { - fwrite(titleContentInfos, 1, titleContentInfoCnt * sizeof(NcmContentInfo), content_info); - fclose(content_info); - } \ No newline at end of file