diff --git a/code_templates/nsp_dumper_stor.c b/code_templates/nsp_dumper_stor.c index 1e1fce0..d4337ec 100644 --- a/code_templates/nsp_dumper_stor.c +++ b/code_templates/nsp_dumper_stor.c @@ -55,6 +55,7 @@ static options_t options[] = { { "disable linked account requirement", 0 }, { "enable screenshots", 0 }, { "enable video capture", 0 }, + { "disable hdcp", 0 }, { "output device", 0 } }; @@ -135,7 +136,8 @@ static void nspDump(TitleInfo *title_info, u64 free_space) bool patch_sua = (options[4].val == 1); bool patch_screenshot = (options[5].val == 1); bool patch_video_capture = (options[6].val == 1); - UsbHsFsDevice *ums_device = (options[7].val == 0 ? NULL : &(ums_devices[options[7].val - 1])); + bool patch_hdcp = (options[7].val == 1); + UsbHsFsDevice *ums_device = (options[8].val == 0 ? NULL : &(ums_devices[options[8].val - 1])); bool success = false; if (ums_device && ums_device->write_protect) @@ -321,7 +323,7 @@ static void nspDump(TitleInfo *title_info, u64 free_space) goto end; } - if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture)) + if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture, patch_hdcp)) { consolePrint("nacp nca patch failed (%s)\n", cur_nca_ctx->content_id_str); goto end; @@ -1087,6 +1089,7 @@ int main(int argc, char *argv[]) } else { selected_idx = (menu == 0 ? title_idx : type_idx); scroll = (menu == 0 ? title_scroll : type_scroll); + if (menu == 0) titleFreeUserApplicationData(&user_app_data); } } else if ((btn_down & (HidNpadButton_Left | HidNpadButton_Right)) && menu == 2 && selected_idx != 0) diff --git a/code_templates/nsp_dumper_usb.c b/code_templates/nsp_dumper_usb.c index 8159551..5057fc2 100644 --- a/code_templates/nsp_dumper_usb.c +++ b/code_templates/nsp_dumper_usb.c @@ -62,7 +62,8 @@ static options_t options[] = { { "change acid rsa key/sig", false }, { "disable linked account requirement", false }, { "enable screenshots", false }, - { "enable video capture", false } + { "enable video capture", false }, + { "disable hdcp", false } }; static const u32 options_count = MAX_ELEMENTS(options); @@ -127,6 +128,7 @@ static void dump_thread_func(void *arg) bool patch_sua = options[4].val; bool patch_screenshot = options[5].val; bool patch_video_capture = options[6].val; + bool patch_hdcp = options[7].val; bool success = false; u8 *buf = NULL; @@ -311,7 +313,7 @@ static void dump_thread_func(void *arg) goto end; } - if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture)) + if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture, patch_hdcp)) { consolePrint("nacp nca patch failed (%s)\n", cur_nca_ctx->content_id_str); goto end; @@ -1189,6 +1191,7 @@ int main(int argc, char *argv[]) } else { selected_idx = (menu == 0 ? title_idx : type_idx); scroll = (menu == 0 ? title_scroll : type_scroll); + if (menu == 0) titleFreeUserApplicationData(&user_app_data); } } else if ((btn_down & (HidNpadButton_Left | HidNpadButton_Right)) && menu == 2 && selected_idx != 0) diff --git a/code_templates/sd_romfs_dumper.c b/code_templates/sd_romfs_dumper.c index dd82880..f71672f 100644 --- a/code_templates/sd_romfs_dumper.c +++ b/code_templates/sd_romfs_dumper.c @@ -478,6 +478,7 @@ int main(int argc, char *argv[]) { consolePrint("\nthe selected title doesn't have available base content.\n"); utilsSleep(3); + titleFreeUserApplicationData(&user_app_data); continue; } @@ -682,6 +683,8 @@ out2: if (base_nca_ctx) free(base_nca_ctx); + titleFreeUserApplicationData(&user_app_data); + if (buf) free(buf); if (app_metadata) free(app_metadata); diff --git a/code_templates/system_title_dumper.c b/code_templates/system_title_dumper.c index 37d356c..ec4159d 100644 --- a/code_templates/system_title_dumper.c +++ b/code_templates/system_title_dumper.c @@ -455,6 +455,7 @@ int main(int argc, char *argv[]) } else { selected_idx = (menu == 0 ? title_idx : nca_idx); scroll = (menu == 0 ? title_scroll : 0); + if (menu == 0) titleFreeTitleInfo(&cur_title_info); } } diff --git a/code_templates/usb_gc_dumper.c b/code_templates/usb_gc_dumper.c index 2859028..cb95b2b 100644 --- a/code_templates/usb_gc_dumper.c +++ b/code_templates/usb_gc_dumper.c @@ -371,17 +371,39 @@ static u32 menuGetElementCount(const Menu *menu) return cnt; } -static void waitForGameCardAndUsb(void) +static bool waitForGameCardAndUsb(void) { consoleClear(); consolePrint("waiting for gamecard...\n"); + u8 status = GameCardStatus_NotInserted; + while(true) { - if (gamecardGetStatus() == GameCardStatus_InsertedAndInfoLoaded) break; + status = gamecardGetStatus(); + if (status > GameCardStatus_NotInserted) break; } - titleIsGameCardInfoUpdated(); + switch(status) + { + case GameCardStatus_NoGameCardPatchEnabled: + consolePrint("\"nogc\" patch enabled, please disable it and reboot your console\n"); + break; + case GameCardStatus_LotusAsicFirmwareUpdateRequired: + consolePrint("gamecard controller firmware update required, please update your console\n"); + break; + case GameCardStatus_InsertedAndInfoNotLoaded: + consolePrint("unexpected I/O error occurred, please check the logfile\n"); + break; + default: + break; + } + + if (status != GameCardStatus_InsertedAndInfoLoaded) + { + utilsWaitForButtonPress(0); + return false; + } consolePrint("waiting for usb session...\n"); @@ -389,6 +411,8 @@ static void waitForGameCardAndUsb(void) { if (usbIsReady()) break; } + + return true; } static bool sendFileData(const char *path, void *data, size_t data_size) @@ -434,7 +458,7 @@ static bool dumpGameCardKeyArea(GameCardKeyArea *out) static bool sendGameCardKeyAreaViaUsb(void) { - waitForGameCardAndUsb(); + if (!waitForGameCardAndUsb()) return false; utilsChangeHomeButtonBlockStatus(false); @@ -466,7 +490,7 @@ end: static bool sendGameCardCertificateViaUsb(void) { - waitForGameCardAndUsb(); + if (!waitForGameCardAndUsb()) return false; utilsChangeHomeButtonBlockStatus(true); @@ -504,7 +528,7 @@ end: static bool sendGameCardImageViaUsb(void) { - waitForGameCardAndUsb(); + if (!waitForGameCardAndUsb()) return false; utilsChangeHomeButtonBlockStatus(true); diff --git a/code_templates/usb_romfs_dumper.c b/code_templates/usb_romfs_dumper.c index cbd3800..bea47bc 100644 --- a/code_templates/usb_romfs_dumper.c +++ b/code_templates/usb_romfs_dumper.c @@ -451,6 +451,7 @@ int main(int argc, char *argv[]) { consolePrint("\nthe selected title doesn't have available base content.\n"); utilsSleep(3); + titleFreeUserApplicationData(&user_app_data); continue; } @@ -677,6 +678,8 @@ out2: if (base_nca_ctx) free(base_nca_ctx); + titleFreeUserApplicationData(&user_app_data); + if (buf) free(buf); if (app_metadata) free(app_metadata); diff --git a/code_templates/xml_generator.c b/code_templates/xml_generator.c index 01d0ac9..5c86a0f 100644 --- a/code_templates/xml_generator.c +++ b/code_templates/xml_generator.c @@ -177,6 +177,7 @@ int main(int argc, char *argv[]) { consolePrint("\nthe selected title doesn't have available base content.\n"); utilsSleep(3); + titleFreeUserApplicationData(&user_app_data); continue; } @@ -445,6 +446,8 @@ out2: if (nca_ctx) free(nca_ctx); + titleFreeUserApplicationData(&user_app_data); + if (app_metadata) free(app_metadata); out: diff --git a/include/core/gamecard.h b/include/core/gamecard.h index 79ffffe..ab8cef6 100644 --- a/include/core/gamecard.h +++ b/include/core/gamecard.h @@ -190,9 +190,13 @@ typedef struct { NXDT_ASSERT(GameCardHeader, 0x200); typedef enum { - GameCardStatus_NotInserted = 0, - GameCardStatus_InsertedAndInfoNotLoaded = 1, ///< Most likely related to the "nogc" patch being enabled. Means nothing at all can be done with the inserted gamecard. - GameCardStatus_InsertedAndInfoLoaded = 2 + GameCardStatus_NotInserted = 0, ///< No gamecard is inserted. + GameCardStatus_NoGameCardPatchEnabled = 1, ///< A gamecard has been inserted, but the running CFW enabled the "nogc" patch at boot. + ///< This triggers an error whenever fsDeviceOperatorGetGameCardHandle is called. Nothing at all can be done with the inserted gamecard. + GameCardStatus_LotusAsicFirmwareUpdateRequired = 2, ///< A gamecard has been inserted, but a LAFW update is needed before being able to read the secure storage area. + ///< Operations on the normal storage area are still possible, though. + GameCardStatus_InsertedAndInfoNotLoaded = 3, ///< A gamecard has been inserted, but an unexpected error unrelated to both "nogc" patch and LAFW version occurred. + GameCardStatus_InsertedAndInfoLoaded = 4 ///< A gamecard has been inserted and all required information could be successfully retrieved from it. } GameCardStatus; typedef enum { diff --git a/include/core/nacp.h b/include/core/nacp.h index 0f4e0a1..c0c6258 100644 --- a/include/core/nacp.h +++ b/include/core/nacp.h @@ -103,10 +103,14 @@ typedef enum { NacpSupportedLanguage_Portuguese = BIT(10), NacpSupportedLanguage_Russian = BIT(11), NacpSupportedLanguage_Korean = BIT(12), - NacpSupportedLanguage_TraditionalChinese = BIT(13), ///< Old: NacpSupportedLanguage_Taiwanese. - NacpSupportedLanguage_SimplifiedChinese = BIT(14), ///< Old: NacpSupportedLanguage_Chinese. + NacpSupportedLanguage_TraditionalChinese = BIT(13), + NacpSupportedLanguage_SimplifiedChinese = BIT(14), NacpSupportedLanguage_BrazilianPortuguese = BIT(15), - NacpSupportedLanguage_Count = 16 ///< Total values supported by this enum. Should always match NacpLanguage_Count. + NacpSupportedLanguage_Count = 16, ///< Total values supported by this enum. Should always match NacpLanguage_Count. + + ///< Old. + NacpSupportedLanguage_Taiwanese = NacpSupportedLanguage_TraditionalChinese, + NacpSupportedLanguage_Chinese = NacpSupportedLanguage_SimplifiedChinese } NacpSupportedLanguage; typedef enum { @@ -390,7 +394,8 @@ bool nacpInitializeContext(NacpContext *out, NcaContext *nca_ctx); /// If 'patch_sua' is true, StartupUserAccount is set to None, the IsOptional bit in StartupUserAccountOption is cleared and UserAccountSwitchLock is set to Disable. /// If 'patch_screenshot' is true, Screenshot is set to Allow. /// If 'patch_video_capture' is true, VideoCapture is set to Enable. -bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_screenshot, bool patch_video_capture); +/// If 'patch_hdcp' is true, Hdcp is set to None. +bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_screenshot, bool patch_video_capture, bool patch_hdcp); /// Writes data from the RomFS file entry patch in the input NacpContext to the provided buffer. void nacpWriteNcaPatch(NacpContext *nacp_ctx, void *buf, u64 buf_size, u64 buf_offset); diff --git a/include/core/nxdt_utils.h b/include/core/nxdt_utils.h index ab525bc..7960974 100644 --- a/include/core/nxdt_utils.h +++ b/include/core/nxdt_utils.h @@ -107,8 +107,9 @@ void utilsJoinThread(Thread *thread); /// If the buffer isn't big enough to hold both its current contents and the new formatted string, it will be resized. __attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(char **dst, size_t *dst_size, const char *fmt, ...); -/// Replaces illegal FAT characters in the provided string with underscores. -/// If 'ascii_only' is set to true, all characters outside the (0x20,0x7E] range will also be replaced with underscores. +/// Replaces illegal FAT characters in the provided UTF-8 string with underscores. +/// If 'ascii_only' is set to true, all codepoints outside the (0x20,0x7E] range will also be replaced with underscores. +/// Replacements are performed on a per-codepoint basis, which means the string length can be reduced by this function. void utilsReplaceIllegalCharacters(char *str, bool ascii_only); /// Trims whitespace characters from the provided string. @@ -139,7 +140,10 @@ bool utilsCreateConcatenationFile(const char *path); /// If 'create_last_element' is true, the last element from the provided path will be created as well. void utilsCreateDirectoryTree(const char *path, bool create_last_element); -/// Returns a pointer to a dynamically allocated string that holds the full path formed by the provided arguments. +/// Returns a pointer to a dynamically allocated string that holds the full path formed by the provided arguments. Both path prefix and file extension are optional. +/// If any elements from the generated path exceed safe filesystem limits, each exceeding element will be truncated. Truncations, if needed, are performed on a per-codepoint basis (UTF-8). +/// If an extension is provided, it will always be preserved, regardless of any possible truncations being carried out. +/// Furthermore, if the full length for the generated path is >= FS_MAX_PATH, NULL will be returned. char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension); /// Simple wrapper to sleep the current thread for a specific number of full seconds. diff --git a/include/core/title.h b/include/core/title.h index a3e5aa0..19ce60d 100644 --- a/include/core/title.h +++ b/include/core/title.h @@ -45,7 +45,7 @@ typedef struct { u8 *icon; ///< JPEG icon data. } TitleApplicationMetadata; -/// Generated using ncm databases. +/// Generated using ncm calls. typedef struct _TitleInfo { u8 storage_id; ///< NcmStorageId. NcmContentMetaKey meta_key; ///< Used with ncm calls. @@ -91,42 +91,49 @@ NcmContentMetaDatabase *titleGetNcmDatabaseByStorageId(u8 storage_id); /// Returns a pointer to a ncm storage handle using a NcmStorageId value. NcmContentStorage *titleGetNcmStorageByStorageId(u8 storage_id); -/// Returns a pointer to a dynamically allocated array of pointers to TitleApplicationMetadata entries, as well as their count. The allocated buffer must be freed by the calling function. +/// Returns a pointer to a dynamically allocated array of pointers to TitleApplicationMetadata entries, as well as their count. Returns NULL if an error occurs. /// If 'is_system' is true, TitleApplicationMetadata entries from available system titles (NcmStorageId_BuiltInSystem) will be returned. /// Otherwise, TitleApplicationMetadata entries from user applications with available content data (NcmStorageId_BuiltInUser, NcmStorageId_SdCard, NcmStorageId_GameCard) will be returned. -/// Returns NULL if an error occurs. +/// The allocated buffer must be freed by the calling function using free(). TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count); -/// Returns a pointer to a TitleInfo entry with a matching storage ID and title ID. +/// Returns a pointer to a dynamically allocated TitleInfo element with a matching storage ID and title ID. Returns NULL if an error occurs. /// If NcmStorageId_Any is used, the first entry with a matching title ID is returned. -/// Returns NULL if an error occurs. +/// Use titleFreeTitleInfo() to free the returned data. TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id); -/// Populates a TitleUserApplicationData element using a user application ID. +/// Frees a dynamically allocated TitleInfo element. +void titleFreeTitleInfo(TitleInfo **info); + +/// Populates a TitleUserApplicationData element with dynamically allocated data using a user application ID. +/// Use titleFreeUserApplicationData() to free the populated data. bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out); +/// Frees data populated by titleGetUserApplicationData(). +void titleFreeUserApplicationData(TitleUserApplicationData *user_app_data); + /// Returns true if orphan titles are available. /// Orphan titles are patches or add-on contents with no NsApplicationControlData available for their parent user application ID. bool titleAreOrphanTitlesAvailable(void); -/// Returns a pointer to a dynamically allocated array of pointers to TitleInfo entries from orphan titles, as well as their count. The allocated buffer must be freed by the calling function. -/// Returns NULL if an error occurs. -TitleInfo **titleGetInfoFromOrphanTitles(u32 *out_count); +/// Returns a pointer to a dynamically allocated array of orphan TitleInfo entries, as well as their count. Returns NULL if an error occurs. +/// Use titleFreeOrphanTitles() to free the returned data. +TitleInfo **titleGetOrphanTitles(u32 *out_count); + +/// Frees orphan title info data returned by titleGetInfoFromOrphanTitles(). +void titleFreeOrphanTitles(TitleInfo ***orphan_info); /// Checks if a gamecard status update has been detected by the background gamecard title info thread (e.g. after a new gamecard has been inserted, of after the current one has been taken out). -/// If so, gamecard title info entries will be updated or freed during this call, depending on the current gamecard status. -/// If this function returns true and titleGetApplicationMetadataEntries() or titleGetInfoFromOrphanTitles() have been previously called: -/// 1. Their returned buffers should be freed. +/// If this function returns true and functions such as titleGetInfoFromStorageByTitleId(), titleGetUserApplicationData() or titleGetInfoFromOrphanTitles() have been previously called: +/// 1. Their returned data must be freed. /// 2. They must be called again. bool titleIsGameCardInfoUpdated(void); -/// Returns a pointer to a dynamically allocated buffer that holds a filename string suitable for output title dumps. -/// Returns NULL if an error occurs. +/// Returns a pointer to a dynamically allocated buffer that holds a filename string suitable for output title dumps. Returns NULL if an error occurs. char *titleGenerateFileName(const TitleInfo *title_info, u8 name_convention, u8 illegal_char_replace_type); -/// Returns a pointer to a dynamically allocated buffer that holds a filename string suitable for output gamecard dumps. +/// Returns a pointer to a dynamically allocated buffer that holds a filename string suitable for output gamecard dumps. Returns NULL if an error occurs. /// A valid gamecard must be inserted, and title info must have been loaded from it accordingly. -/// Returns NULL if an error occurs. char *titleGenerateGameCardFileName(u8 name_convention, u8 illegal_char_replace_type); /// Returns a pointer to a string holding the name of the provided NcmContentType value. Returns NULL if the provided value is invalid. diff --git a/source/core/bfttf.c b/source/core/bfttf.c index 44ede6a..4462b8e 100644 --- a/source/core/bfttf.c +++ b/source/core/bfttf.c @@ -97,7 +97,13 @@ bool bfttfInitialize(void) } /* Initialize NCA context. */ - if (!ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), NULL)) + bool nca_ctx_init = ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), NULL); + + /* Free title info. */ + titleFreeTitleInfo(&title_info); + + /* Check if NCA context initialization succeeded. */ + if (!nca_ctx_init) { LOG_MSG("Failed to initialize Data NCA context for %016lX!", font_info->title_id); continue; diff --git a/source/core/gamecard.c b/source/core/gamecard.c index 751d022..bcba7cc 100644 --- a/source/core/gamecard.c +++ b/source/core/gamecard.c @@ -55,7 +55,7 @@ typedef enum { } GameCardCapacity; /// Only kept for documentation purposes, not really used. -/// A copy of the gamecard header without the RSA-2048 signature and a plaintext GameCardHeaderEncryptedArea precedes this struct in FS program memory. +/// A copy of the gamecard header without the RSA-2048 signature and a plaintext GameCardInfo precedes this struct in FS program memory. typedef struct { u32 memory_interface_mode; u32 asic_status; @@ -114,16 +114,18 @@ static u64 g_lafwVersion = 0; static Thread g_gameCardDetectionThread = {0}; static UEvent g_gameCardDetectionThreadExitEvent = {0}, g_gameCardStatusChangeEvent = {0}; -static bool g_gameCardDetectionThreadCreated = false, g_gameCardInserted = false, g_gameCardInfoLoaded = false; +static bool g_gameCardDetectionThreadCreated = false; + +static GameCardStatus g_gameCardStatus = GameCardStatus_NotInserted; static FsGameCardHandle g_gameCardHandle = {0}; static FsStorage g_gameCardStorage = {0}; -static u8 g_gameCardStorageCurrentArea = GameCardStorageArea_None; +static u8 g_gameCardCurrentStorageArea = GameCardStorageArea_None; static u8 *g_gameCardReadBuf = NULL; static GameCardHeader g_gameCardHeader = {0}; static GameCardInfo g_gameCardInfoArea = {0}; -static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0, g_gameCardStorageTotalSize = 0; +static u64 g_gameCardNormalAreaSize = 0, g_gameCardSecureAreaSize = 0, g_gameCardTotalSize = 0; static u64 g_gameCardCapacity = 0; static u32 g_gameCardHfsCount = 0; @@ -156,7 +158,11 @@ static void gamecardDetectionThreadFunc(void *arg); NX_INLINE bool gamecardIsInserted(void); static void gamecardLoadInfo(void); -static void gamecardFreeInfo(void); +static void gamecardFreeInfo(bool clear_status); + +static bool gamecardReadHeader(void); + +static bool gamecardGetDecryptedCardInfoArea(void); static bool gamecardReadInitialData(GameCardKeyArea *out); @@ -170,8 +176,6 @@ static void gamecardCloseStorageArea(void); static bool gamecardGetStorageAreasSizes(void); NX_INLINE u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size); -static bool gamecardGetDecryptedCardInfoArea(void); - static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char *name, u64 offset, u64 size, u8 *hash, u64 hash_target_offset, u32 hash_target_size); static HashFileSystemContext *_gamecardGetHashFileSystemContext(u8 hfs_partition_type); @@ -303,7 +307,7 @@ u8 gamecardGetStatus(void) SCOPED_LOCK(&g_gameCardMutex) { - if (g_gameCardInserted) status = (g_gameCardInfoLoaded ? GameCardStatus_InsertedAndInfoLoaded : GameCardStatus_InsertedAndInfoNotLoaded); + if (g_gameCardInterfaceInit) status = g_gameCardStatus; } return status; @@ -333,7 +337,7 @@ bool gamecardGetHeader(GameCardHeader *out) SCOPED_LOCK(&g_gameCardMutex) { - ret = (g_gameCardInserted && g_gameCardInfoLoaded && out); + ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out); if (ret) memcpy(out, &g_gameCardHeader, sizeof(GameCardHeader)); } @@ -346,17 +350,11 @@ bool gamecardGetCertificate(FsGameCardCertificate *out) SCOPED_LOCK(&g_gameCardMutex) { - if (!g_gameCardInserted || !g_gameCardHandle.value || !out) break; + if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHandle.value || !out) break; /* Read the gamecard certificate using the official IPC call. */ Result rc = fsDeviceOperatorGetGameCardDeviceCertificate(&g_deviceOperator, &g_gameCardHandle, out); - if (R_FAILED(rc)) - { - LOG_MSG("fsDeviceOperatorGetGameCardDeviceCertificate failed! (0x%08X)", rc); - - /* Attempt to manually read the gamecard certificate. */ - if (gamecardReadStorageArea(out, sizeof(FsGameCardCertificate), GAMECARD_CERTIFICATE_OFFSET)) rc = 0; - } + if (R_FAILED(rc)) LOG_MSG("fsDeviceOperatorGetGameCardDeviceCertificate failed! (0x%08X)", rc); ret = R_SUCCEEDED(rc); } @@ -370,8 +368,8 @@ bool gamecardGetTotalSize(u64 *out) SCOPED_LOCK(&g_gameCardMutex) { - ret = (g_gameCardInserted && g_gameCardInfoLoaded && out); - if (ret) *out = g_gameCardStorageTotalSize; + ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out); + if (ret) *out = g_gameCardTotalSize; } return ret; @@ -383,7 +381,7 @@ bool gamecardGetTrimmedSize(u64 *out) SCOPED_LOCK(&g_gameCardMutex) { - ret = (g_gameCardInserted && g_gameCardInfoLoaded && out); + ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out); if (ret) *out = (sizeof(GameCardHeader) + GAMECARD_PAGE_OFFSET(g_gameCardHeader.valid_data_end_address)); } @@ -396,7 +394,7 @@ bool gamecardGetRomCapacity(u64 *out) SCOPED_LOCK(&g_gameCardMutex) { - ret = (g_gameCardInserted && g_gameCardInfoLoaded && out); + ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out); if (ret) *out = g_gameCardCapacity; } @@ -409,7 +407,7 @@ bool gamecardGetBundledFirmwareUpdateVersion(VersionType1 *out) SCOPED_LOCK(&g_gameCardMutex) { - if (!g_gameCardInserted || !g_gameCardHandle.value || !out) break; + if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHandle.value || !out) break; u64 update_id = 0; u32 update_version = 0; @@ -544,7 +542,7 @@ static bool gamecardGetLotusAsicFirmwareVersion(void) if (!found) { - LOG_MSG("Unable to locate Lotus ReadFw blob in FS .data segment!"); + LOG_MSG("Unable to locate Lotus %s blob in FS .data segment!", dev_unit ? "ReadDevFw" : "ReadFw"); goto end; } @@ -602,12 +600,10 @@ static void gamecardDetectionThreadFunc(void *arg) /* Load gamecard info right away if a gamecard is inserted, then signal the user mode gamecard status change event. */ SCOPED_LOCK(&g_gameCardMutex) { - g_gameCardInserted = gamecardIsInserted(); - if (g_gameCardInserted) gamecardLoadInfo(); + if (gamecardIsInserted()) gamecardLoadInfo(); + ueventSignal(&g_gameCardStatusChangeEvent); } - ueventSignal(&g_gameCardStatusChangeEvent); - while(true) { /* Wait until an event is triggered. */ @@ -620,12 +616,11 @@ static void gamecardDetectionThreadFunc(void *arg) SCOPED_LOCK(&g_gameCardMutex) { /* Free gamecard info before proceeding. */ - gamecardFreeInfo(); + gamecardFreeInfo(true); /* Retrieve current gamecard insertion status. */ /* Only proceed if we're dealing with a status change. */ - g_gameCardInserted = gamecardIsInserted(); - if (g_gameCardInserted) + if (gamecardIsInserted()) { /* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules. */ utilsSleep(GAMECARD_ACCESS_WAIT_TIME); @@ -633,15 +628,14 @@ static void gamecardDetectionThreadFunc(void *arg) /* Load gamecard info. */ gamecardLoadInfo(); } + + /* Signal user mode gamecard status change event. */ + ueventSignal(&g_gameCardStatusChangeEvent); } - - /* Signal user mode gamecard status change event. */ - ueventSignal(&g_gameCardStatusChangeEvent); } /* Free gamecard info and close gamecard handle. */ - gamecardFreeInfo(); - g_gameCardInserted = false; + gamecardFreeInfo(true); threadExit(); } @@ -656,87 +650,45 @@ NX_INLINE bool gamecardIsInserted(void) static void gamecardLoadInfo(void) { - if (g_gameCardInfoLoaded) return; + if (g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded) return; HashFileSystemContext *root_fs_ctx = NULL; u32 root_fs_entry_count = 0, root_fs_name_table_size = 0; char *root_fs_name_table = NULL; - bool dump_gamecard_header = false; + + /* Set initial gamecard status. */ + g_gameCardStatus = GameCardStatus_InsertedAndInfoNotLoaded; + + /* Read gamecard header. */ + /* This step *will* fail if the running CFW enabled the "nogc" patch. */ + /* gamecardGetHandleAndStorage() takes care of updating the gamecard status accordingly if this happens. */ + if (!gamecardReadHeader()) goto end; + + /* Get decrypted CardInfo area from header. */ + if (!gamecardGetDecryptedCardInfoArea()) goto end; + + /* Check if we meet the Lotus ASIC firmware (LAFW) version requirement. */ + /* Lotus treats the GameCardFwVersion field as the maximum unsupported LAFW version, instead of treating it as the minimum supported version. */ + if (g_lafwVersion <= g_gameCardInfoArea.fw_version) + { + LOG_MSG("LAFW version doesn't meet gamecard requirement! (%lu <= %lu).", g_lafwVersion, g_gameCardInfoArea.fw_version); + g_gameCardStatus = GameCardStatus_LotusAsicFirmwareUpdateRequired; + goto end; + } /* Retrieve gamecard storage area sizes. */ - /* gamecardReadStorageArea() actually checks if the storage area sizes are greater than zero, so we must first perform this step. */ + /* gamecardReadStorageArea() actually checks if the storage area sizes are greater than zero, so we must perform this step. */ if (!gamecardGetStorageAreasSizes()) { LOG_MSG("Failed to retrieve gamecard storage area sizes!"); goto end; } - /* Read gamecard header. */ - if (!gamecardReadStorageArea(&g_gameCardHeader, sizeof(GameCardHeader), 0)) - { - LOG_MSG("Failed to read gamecard header!"); - goto end; - } - - /* Check magic word from gamecard header. */ - if (__builtin_bswap32(g_gameCardHeader.magic) != GAMECARD_HEAD_MAGIC) - { - LOG_MSG("Invalid gamecard header magic word! (0x%08X).", __builtin_bswap32(g_gameCardHeader.magic)); - dump_gamecard_header = true; - goto end; - } - - - - - - - - - - - - - - - - /* Get decrypted CardInfo area. */ - if (!gamecardGetDecryptedCardInfoArea()) goto end; - - LOG_DATA(&g_gameCardInfoArea, sizeof(GameCardInfo), "Gamecard CardInfo area dump:"); - - /* Check if we meet the Lotus ASIC firmware (LAFW) version requirement. */ - /* Lotus treats the GameCardFwVersion field as the maximum unsupported LAFW version, instead of treating it as the minimum supported version. */ - /* TODO: move this check somewhere else. We're supposed to do it only if things go south while reading gamecard data. */ - if (g_lafwVersion <= g_gameCardInfoArea.fw_version) - { - LOG_MSG("LAFW version not supported by the inserted gamecard (%lu <= %lu).", g_lafwVersion, g_gameCardInfoArea.fw_version); - goto end; - } - - - - - - - - - - - - - - - - - - /* Get gamecard capacity. */ g_gameCardCapacity = gamecardGetCapacityFromRomSizeValue(g_gameCardHeader.rom_size); if (!g_gameCardCapacity) { LOG_MSG("Invalid gamecard capacity value! (0x%02X).", g_gameCardHeader.rom_size); - dump_gamecard_header = true; goto end; } @@ -744,7 +696,7 @@ static void gamecardLoadInfo(void) { /* The total size for the secure storage area is maxed out under SX OS. */ /* Let's try to calculate it manually. */ - g_gameCardStorageSecureAreaSize = (g_gameCardCapacity - (g_gameCardStorageNormalAreaSize + GAMECARD_UNUSED_AREA_SIZE(g_gameCardCapacity))); + g_gameCardSecureAreaSize = (g_gameCardCapacity - (g_gameCardNormalAreaSize + GAMECARD_UNUSED_AREA_SIZE(g_gameCardCapacity))); } /* Initialize Hash FS context for the root partition. */ @@ -787,30 +739,29 @@ static void gamecardLoadInfo(void) if (!g_gameCardHfsCtx[i + 1]) goto end; } - g_gameCardInfoLoaded = true; + /* Update gamecard status. */ + g_gameCardStatus = GameCardStatus_InsertedAndInfoLoaded; end: - if (!g_gameCardInfoLoaded) + if (g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded) { - if (dump_gamecard_header) LOG_DATA(&g_gameCardHeader, sizeof(GameCardHeader), "Gamecard header dump:"); - if (!g_gameCardHfsCtx && root_fs_ctx) { hfsFreeContext(root_fs_ctx); free(root_fs_ctx); } - gamecardFreeInfo(); + gamecardFreeInfo(false); } } -static void gamecardFreeInfo(void) +static void gamecardFreeInfo(bool clear_status) { memset(&g_gameCardHeader, 0, sizeof(GameCardHeader)); memset(&g_gameCardInfoArea, 0, sizeof(GameCardInfo)); - g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = g_gameCardStorageTotalSize = 0; + g_gameCardNormalAreaSize = g_gameCardSecureAreaSize = g_gameCardTotalSize = 0; g_gameCardCapacity = 0; @@ -834,12 +785,70 @@ static void gamecardFreeInfo(void) gamecardCloseStorageArea(); - g_gameCardInfoLoaded = false; + if (clear_status) g_gameCardStatus = GameCardStatus_NotInserted; +} + +static bool gamecardReadHeader(void) +{ + /* Open normal storage area. */ + if (!gamecardOpenStorageArea(GameCardStorageArea_Normal)) + { + LOG_MSG("Failed to open normal storage area!"); + return false; + } + + /* Read gamecard header. */ + /* This step doesn't rely on gamecardReadStorageArea() because of its dependence on storage area sizes (which we haven't retrieved). */ + Result rc = fsStorageRead(&g_gameCardStorage, 0, &g_gameCardHeader, sizeof(GameCardHeader)); + if (R_FAILED(rc)) + { + LOG_MSG("fsStorageRead failed to read gamecard header! (0x%08X).", rc); + return false; + } + + //LOG_DATA(&g_gameCardHeader, sizeof(GameCardHeader), "Gamecard header dump:"); + + /* Check magic word from gamecard header. */ + if (__builtin_bswap32(g_gameCardHeader.magic) != GAMECARD_HEAD_MAGIC) + { + LOG_MSG("Invalid gamecard header magic word! (0x%08X).", __builtin_bswap32(g_gameCardHeader.magic)); + return false; + } + + return true; +} + +static bool gamecardGetDecryptedCardInfoArea(void) +{ + const u8 *card_info_key = NULL; + u8 card_info_iv[AES_128_KEY_SIZE] = {0}; + Aes128CbcContext aes_ctx = {0}; + + /* Retrieve CardInfo area key. */ + card_info_key = keysGetGameCardInfoKey(); + if (!card_info_key) + { + LOG_MSG("Failed to retrieve CardInfo area key!"); + return false; + } + + /* Reverse CardInfo IV. */ + for(u8 i = 0; i < AES_128_KEY_SIZE; i++) card_info_iv[i] = g_gameCardHeader.card_info_iv[AES_128_KEY_SIZE - i - 1]; + + /* Initialize AES-128-CBC context. */ + aes128CbcContextCreate(&aes_ctx, card_info_key, card_info_iv, false); + + /* Decrypt CardInfo area. */ + aes128CbcDecrypt(&aes_ctx, &g_gameCardInfoArea, &(g_gameCardHeader.card_info), sizeof(GameCardInfo)); + + //LOG_DATA(&g_gameCardInfoArea, sizeof(GameCardInfo), "Gamecard CardInfo area dump:"); + + return true; } static bool gamecardReadInitialData(GameCardKeyArea *out) { - if (!g_gameCardInserted || !g_gameCardInfoLoaded || !out) + if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !out) { LOG_MSG("Invalid parameters!"); return false; @@ -891,7 +900,7 @@ static bool gamecardReadInitialData(GameCardKeyArea *out) static bool gamecardGetHandleAndStorage(u32 partition) { - if (!g_gameCardInserted || partition > 1) + if (g_gameCardStatus < GameCardStatus_InsertedAndInfoNotLoaded || partition > 1) { LOG_MSG("Invalid parameters!"); return false; @@ -906,7 +915,7 @@ static bool gamecardGetHandleAndStorage(u32 partition) if (R_FAILED(rc)) svcSleepThread(100000000); /* First, let's try to retrieve a gamecard handle. */ - /* This can return 0x140A02 if the "nogc" patch is enabled by the running CFW. */ + /* This can return an error if the "nogc" patch is enabled by the running CFW (most commonly 0x140A02). */ rc = fsDeviceOperatorGetGameCardHandle(&g_deviceOperator, &g_gameCardHandle); if (R_FAILED(rc)) { @@ -927,7 +936,11 @@ static bool gamecardGetHandleAndStorage(u32 partition) break; } - if (R_FAILED(rc)) LOG_MSG("fsDeviceOperatorGetGameCardHandle / fsOpenGameCardStorage failed! (0x%08X).", rc); + if (R_FAILED(rc)) + { + LOG_MSG("fsDeviceOperatorGetGameCardHandle / fsOpenGameCardStorage failed! (0x%08X).", rc); + if (g_gameCardStatus == GameCardStatus_InsertedAndInfoNotLoaded && partition == 0) g_gameCardStatus = GameCardStatus_NoGameCardPatchEnabled; + } return R_SUCCEEDED(rc); } @@ -942,14 +955,14 @@ NX_INLINE void gamecardCloseHandle(void) static bool gamecardOpenStorageArea(u8 area) { - if (!g_gameCardInserted || (area != GameCardStorageArea_Normal && area != GameCardStorageArea_Secure)) + if (g_gameCardStatus < GameCardStatus_InsertedAndInfoNotLoaded || (area != GameCardStorageArea_Normal && area != GameCardStorageArea_Secure)) { LOG_MSG("Invalid parameters!"); return false; } /* Return right away if a valid handle has already been retrieved and the desired gamecard storage area is currently open. */ - if (g_gameCardHandle.value && serviceIsActive(&(g_gameCardStorage.s)) && g_gameCardStorageCurrentArea == area) return true; + if (g_gameCardHandle.value && serviceIsActive(&(g_gameCardStorage.s)) && g_gameCardCurrentStorageArea == area) return true; /* Close both gamecard handle and open storage area. */ gamecardCloseStorageArea(); @@ -962,14 +975,14 @@ static bool gamecardOpenStorageArea(u8 area) } /* Update current gamecard storage area. */ - g_gameCardStorageCurrentArea = area; + g_gameCardCurrentStorageArea = area; return true; } static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset) { - if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !read_size || (offset + read_size) > g_gameCardStorageTotalSize) + if (g_gameCardStatus < GameCardStatus_InsertedAndInfoNotLoaded || !g_gameCardNormalAreaSize || !g_gameCardSecureAreaSize || !out || !read_size || (offset + read_size) > g_gameCardTotalSize) { LOG_MSG("Invalid parameters!"); return false; @@ -977,21 +990,21 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset) Result rc = 0; u8 *out_u8 = (u8*)out; - u8 area = (offset < g_gameCardStorageNormalAreaSize ? GameCardStorageArea_Normal : GameCardStorageArea_Secure); + u8 area = (offset < g_gameCardNormalAreaSize ? GameCardStorageArea_Normal : GameCardStorageArea_Secure); bool success = false; /* Handle reads that span both the normal and secure gamecard storage areas. */ - if (area == GameCardStorageArea_Normal && (offset + read_size) > g_gameCardStorageNormalAreaSize) + if (area == GameCardStorageArea_Normal && (offset + read_size) > g_gameCardNormalAreaSize) { /* Calculate normal storage area size difference. */ - u64 diff_size = (g_gameCardStorageNormalAreaSize - offset); + u64 diff_size = (g_gameCardNormalAreaSize - offset); /* Read normal storage area data. */ if (!gamecardReadStorageArea(out_u8, diff_size, offset)) goto end; /* Adjust variables to read right from the start of the secure storage area. */ read_size -= diff_size; - offset = g_gameCardStorageNormalAreaSize; + offset = g_gameCardNormalAreaSize; out_u8 += diff_size; area = GameCardStorageArea_Secure; } @@ -1004,8 +1017,8 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset) goto end; } - /* Calculate appropiate storage area offset and retrieve the right storage area pointer. */ - u64 base_offset = (area == GameCardStorageArea_Normal ? offset : (offset - g_gameCardStorageNormalAreaSize)); + /* Calculate proper storage area offset. */ + u64 base_offset = (area == GameCardStorageArea_Normal ? offset : (offset - g_gameCardNormalAreaSize)); if (!(base_offset % GAMECARD_PAGE_SIZE) && !(read_size % GAMECARD_PAGE_SIZE)) { @@ -1054,17 +1067,11 @@ static void gamecardCloseStorageArea(void) gamecardCloseHandle(); - g_gameCardStorageCurrentArea = GameCardStorageArea_None; + g_gameCardCurrentStorageArea = GameCardStorageArea_None; } static bool gamecardGetStorageAreasSizes(void) { - if (!g_gameCardInserted) - { - LOG_MSG("Gamecard not inserted!"); - return false; - } - for(u8 i = 0; i < 2; i++) { Result rc = 0; @@ -1084,19 +1091,18 @@ static bool gamecardGetStorageAreasSizes(void) if (R_FAILED(rc) || !area_size) { LOG_MSG("fsStorageGetSize failed to retrieve %s storage area size! (0x%08X).", GAMECARD_STORAGE_AREA_NAME(area), rc); - g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = g_gameCardStorageTotalSize = 0; return false; } if (area == GameCardStorageArea_Normal) { - g_gameCardStorageNormalAreaSize = area_size; + g_gameCardNormalAreaSize = area_size; } else { - g_gameCardStorageSecureAreaSize = area_size; + g_gameCardSecureAreaSize = area_size; } } - g_gameCardStorageTotalSize = (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize); + g_gameCardTotalSize = (g_gameCardNormalAreaSize + g_gameCardSecureAreaSize); return true; } @@ -1132,39 +1138,6 @@ NX_INLINE u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size) return capacity; } -static bool gamecardGetDecryptedCardInfoArea(void) -{ - const u8 *card_info_key = NULL; - u8 card_info_iv[AES_128_KEY_SIZE] = {0}; - Aes128CbcContext aes_ctx = {0}; - - /* Retrieve CardInfo area key. */ - card_info_key = keysGetGameCardInfoKey(); - if (!card_info_key) - { - LOG_MSG("Failed to retrieve CardInfo area key!"); - return false; - } - - /* Reverse CardInfo IV. */ - for(u8 i = 0; i < AES_128_KEY_SIZE; i++) card_info_iv[i] = g_gameCardHeader.card_info_iv[AES_128_KEY_SIZE - i - 1]; - - /* Initialize AES-128-CBC context. */ - aes128CbcContextCreate(&aes_ctx, card_info_key, card_info_iv, false); - - /* Decrypt CardInfo area. */ - aes128CbcDecrypt(&aes_ctx, &g_gameCardInfoArea, &(g_gameCardHeader.card_info), sizeof(GameCardInfo)); - - /* Verify update ID. */ - if (utilsGetCustomFirmwareType() != UtilsCustomFirmwareType_SXOS && g_gameCardInfoArea.upp_id != GAMECARD_UPDATE_TID) - { - LOG_MSG("Failed to decrypt CardInfo area!"); - return false; - } - - return true; -} - static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char *name, u64 offset, u64 size, u8 *hash, u64 hash_target_offset, u32 hash_target_size) { u32 i = 0, magic = 0; @@ -1175,7 +1148,7 @@ static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char bool success = false, dump_fs_header = false; if ((name && !*name) || offset < (GAMECARD_CERTIFICATE_OFFSET + sizeof(FsGameCardCertificate)) || !IS_ALIGNED(offset, GAMECARD_PAGE_SIZE) || \ - (size && (!IS_ALIGNED(size, GAMECARD_PAGE_SIZE) || (offset + size) > g_gameCardStorageTotalSize))) + (size && (!IS_ALIGNED(size, GAMECARD_PAGE_SIZE) || (offset + size) > g_gameCardTotalSize))) { LOG_MSG("Invalid parameters!"); goto end; @@ -1302,7 +1275,7 @@ static HashFileSystemContext *_gamecardGetHashFileSystemContext(u8 hfs_partition HashFileSystemContext *fs_ctx = NULL; const char *partition_name = NULL; - if (!g_gameCardInserted || !g_gameCardInfoLoaded || !g_gameCardHfsCount || !g_gameCardHfsCtx || hfs_partition_type >= GameCardHashFileSystemPartitionType_Count) + if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHfsCount || !g_gameCardHfsCtx || hfs_partition_type >= GameCardHashFileSystemPartitionType_Count) { LOG_MSG("Invalid parameters!"); goto end; diff --git a/source/core/nacp.c b/source/core/nacp.c index a106240..def85d5 100644 --- a/source/core/nacp.c +++ b/source/core/nacp.c @@ -338,7 +338,7 @@ end: return success; } -bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_screenshot, bool patch_video_capture) +bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_screenshot, bool patch_video_capture, bool patch_hdcp) { if (!nacpIsValidContext(nacp_ctx)) { @@ -350,7 +350,7 @@ bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_scre u8 nacp_hash[SHA256_HASH_SIZE] = {0}; /* Check if we're not patching anything. */ - if (!patch_sua && !patch_screenshot && !patch_video_capture) return true; + if (!patch_sua && !patch_screenshot && !patch_video_capture && !patch_hdcp) return true; /* Patch StartupUserAccount, StartupUserAccountOption and UserAccountSwitchLock. */ if (patch_sua) @@ -366,6 +366,9 @@ bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_scre /* Patch VideoCapture. */ if (patch_video_capture) data->video_capture = NacpVideoCapture_Enable; + /* Patch Hdcp. */ + if (patch_hdcp) data->hdcp = NacpHdcp_None; + /* Check if we really need to generate this patch. */ sha256CalculateHash(nacp_hash, data, sizeof(_NacpStruct)); if (!memcmp(nacp_hash, nacp_ctx->data_hash, sizeof(nacp_hash))) diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index 3480a76..7aec4d3 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -31,6 +31,9 @@ #include "bfttf.h" #include "fatfs/ff.h" +/* Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits. */ +#define NT_MAX_FILENAME_LENGTH 255 + /* Global variables. */ static bool g_resourcesInit = false; @@ -78,6 +81,8 @@ static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param); static void utilsPrintConsoleError(void); +static size_t utilsGetUtf8CodepointCount(const char *str, size_t str_size, size_t cp_limit, size_t *last_cp_pos); + bool utilsInitializeResources(const int program_argc, const char **program_argv) { Result rc = 0; @@ -445,14 +450,32 @@ end: void utilsReplaceIllegalCharacters(char *str, bool ascii_only) { - size_t strsize = 0; + size_t str_size = 0, cur_pos = 0; - if (!str || !(strsize = strlen(str))) return; + if (!str || !(str_size = strlen(str))) return; - for(size_t i = 0; i < strsize; i++) + u8 *ptr1 = (u8*)str, *ptr2 = ptr1; + ssize_t units = 0; + u32 code = 0; + + while(cur_pos < str_size) { - if (memchr(g_illegalFileSystemChars, str[i], g_illegalFileSystemCharsLength) || str[i] < 0x20 || (!ascii_only && str[i] == 0x7F) || (ascii_only && str[i] >= 0x7F)) str[i] = '_'; + units = decode_utf8(&code, ptr1); + if (units < 0) break; + + if (memchr(g_illegalFileSystemChars, (int)code, g_illegalFileSystemCharsLength) || code < 0x20 || (!ascii_only && code == 0x7F) || (ascii_only && code >= 0x7F)) + { + *ptr2++ = '_'; + } else { + if (ptr2 != ptr1) memmove(ptr2, ptr1, (size_t)units); + ptr2 += units; + } + + ptr1 += units; + cur_pos += (size_t)units; } + + *ptr2 = '\0'; } void utilsTrimString(char *str) @@ -568,10 +591,10 @@ bool utilsCreateConcatenationFile(const char *path) return false; } - /* Safety check: remove any existant file/directory at the destination path. */ + /* Safety measure: remove any existant file/directory at the destination path. */ utilsRemoveConcatenationFile(path); - /* Create ConcatenationFile */ + /* Create ConcatenationFile. */ /* If the call succeeds, the caller function will be able to operate on this file using stdio calls. */ Result rc = fsdevCreateFile(path, 0, FsCreateOption_BigFile); if (R_FAILED(rc)) LOG_MSG("fsdevCreateFile failed for \"%s\"! (0x%08X).", path, rc); @@ -604,25 +627,113 @@ void utilsCreateDirectoryTree(const char *path, bool create_last_element) char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension) { - if (!filename || !*filename || !extension || !*extension) + if (!filename || !*filename) { LOG_MSG("Invalid parameters!"); return NULL; } - char *path = NULL; - size_t path_len = (strlen(filename) + strlen(extension) + 1); - if (prefix && *prefix) path_len += strlen(prefix); + bool use_prefix = (prefix && *prefix); + size_t prefix_len = (use_prefix ? strlen(prefix) : 0); + bool append_path_sep = (use_prefix && prefix[prefix_len - 1] != '/'); - if (!(path = calloc(path_len, sizeof(char)))) + bool use_extension = (extension && *extension); + size_t extension_len = (use_extension ? strlen(extension) : 0); + bool append_dot = (use_extension && *extension != '.'); + + size_t path_len = (prefix_len + strlen(filename) + extension_len); + if (append_path_sep) path_len++; + if (append_dot) path_len++; + + char *path = NULL, *ptr1 = NULL, *ptr2 = NULL; + bool filename_only = false, success = false; + + /* Allocate memory for the output path. */ + if (!(path = calloc(path_len + 1, sizeof(char)))) { LOG_MSG("Failed to allocate 0x%lX bytes for output path!", path_len); - return NULL; + goto end; } - if (prefix && *prefix) strcat(path, prefix); + /* Generate output path. */ + if (use_prefix) strcat(path, prefix); + if (append_path_sep) strcat(path, "/"); strcat(path, filename); - strcat(path, extension); + if (append_dot) strcat(path, "."); + if (use_extension) strcat(path, extension); + + /* Retrieve pointer to the first path separator. */ + ptr1 = strchr(path, '/'); + if (!ptr1) + { + filename_only = true; + ptr1 = path; + } + + /* Make sure each path element doesn't exceed NT_MAX_FILENAME_LENGTH. */ + while(ptr1) + { + /* End loop if we find a NULL terminator. */ + if (!filename_only && !*ptr1++) break; + + /* Get pointer to next path separator. */ + ptr2 = strchr(ptr1, '/'); + + /* Get current path element size. */ + size_t element_size = (ptr2 ? (size_t)(ptr2 - ptr1) : (path_len - (size_t)(ptr1 - path))); + + /* Get UTF-8 codepoint count. */ + /* Use NT_MAX_FILENAME_LENGTH as the codepoint count limit. */ + size_t last_cp_pos = 0; + size_t cp_count = utilsGetUtf8CodepointCount(ptr1, element_size, NT_MAX_FILENAME_LENGTH, &last_cp_pos); + if (cp_count > NT_MAX_FILENAME_LENGTH) + { + if (ptr2) + { + /* Truncate current element by moving the rest of the path to the current position. */ + memmove(ptr1 + last_cp_pos, ptr2, path_len - (size_t)(ptr2 - path)); + + /* Update pointer. */ + ptr2 -= (element_size - last_cp_pos); + } else + if (use_extension) + { + /* Truncate last element. Make sure to preserve the provided file extension. */ + size_t diff = extension_len; + if (append_dot) diff++; + + if (diff >= last_cp_pos) + { + LOG_MSG("File extension length is >= truncated filename length! (0x%lX >= 0x%lX) (#1).", diff, last_cp_pos); + goto end; + } + + memmove(ptr1 + last_cp_pos - diff, ptr1 + element_size - diff, diff); + } + + path_len -= (element_size - last_cp_pos); + path[path_len] = '\0'; + } + + ptr1 = ptr2; + } + + /* Check if the full length for the generated path is >= FS_MAX_PATH. */ + if (path_len >= FS_MAX_PATH) + { + LOG_MSG("Generated path length is >= FS_MAX_PATH! (0x%lX).", path_len); + goto end; + } + + /* Update flag. */ + success = true; + +end: + if (!success && path) + { + free(path); + path = NULL; + } return path; } @@ -759,3 +870,26 @@ static void utilsPrintConsoleError(void) consoleExit(NULL); } + +static size_t utilsGetUtf8CodepointCount(const char *str, size_t str_size, size_t cp_limit, size_t *last_cp_pos) +{ + if (!str || !*str || !str_size || (!cp_limit && last_cp_pos) || (cp_limit && !last_cp_pos)) return 0; + + u32 code = 0; + ssize_t units = 0; + size_t cur_pos = 0, cp_count = 0; + const u8 *str_u8 = (const u8*)str; + + while(cur_pos < str_size) + { + units = decode_utf8(&code, str_u8 + cur_pos); + size_t new_pos = (cur_pos + (size_t)units); + if (units < 0 || !code || new_pos > str_size) break; + + cp_count++; + cur_pos = new_pos; + if (cp_limit && last_cp_pos && cp_count < cp_limit) *last_cp_pos = cur_pos; + } + + return cp_count; +} diff --git a/source/core/romfs.c b/source/core/romfs.c index e465690..aee97a4 100644 --- a/source/core/romfs.c +++ b/source/core/romfs.c @@ -459,9 +459,13 @@ bool romfsGeneratePathFromDirectoryEntry(RomFileSystemContext *ctx, RomFileSyste strncat(out_path, (*cur_dir_entry)->name, (*cur_dir_entry)->name_length); path_len++; - if (illegal_char_replace_type) utilsReplaceIllegalCharacters(out_path + path_len, illegal_char_replace_type == RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly); - - path_len += (*cur_dir_entry)->name_length; + if (illegal_char_replace_type) + { + utilsReplaceIllegalCharacters(out_path + path_len, illegal_char_replace_type == RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly); + path_len += strlen(out_path + path_len); + } else { + path_len += (*cur_dir_entry)->name_length; + } } success = true; diff --git a/source/core/title.c b/source/core/title.c index 01dbd0c..ee191dd 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -23,7 +23,10 @@ #include "title.h" #include "gamecard.h" -#define NS_APPLICATION_RECORD_LIMIT 4096 +#define NS_APPLICATION_RECORD_LIMIT 4096 + +#define TITLE_STORAGE_COUNT 4 /* GameCard, BuiltInSystem, BuiltInUser, SdCard. */ +#define TITLE_STORAGE_INDEX(storage_id) ((storage_id) - NcmStorageId_GameCard) /* Type definitions. */ @@ -32,30 +35,28 @@ typedef struct { char name[32]; } TitleSystemEntry; +typedef struct { + u8 storage_id; ///< NcmStorageId. + NcmContentMetaDatabase ncm_db; + NcmContentStorage ncm_storage; + TitleInfo **titles; + u32 title_count; +} TitleStorage; + /* Global variables. */ static Mutex g_titleMutex = 0; -static Thread g_titleGameCardInfoThread = {0}; -static UEvent g_titleGameCardInfoThreadExitEvent = {0}, *g_titleGameCardStatusChangeUserEvent = NULL, g_titleGameCardUpdateInfoUserEvent = {0}; -static CondVar g_gameCardCondVar = 0; +static Thread g_titleGameCardInfoThread = {0}; +static UEvent g_titleGameCardInfoThreadExitEvent = {0}, *g_titleGameCardStatusChangeUserEvent = NULL; static bool g_titleInterfaceInit = false, g_titleGameCardInfoThreadCreated = false, g_titleGameCardAvailable = false, g_titleGameCardInfoUpdated = false; static NsApplicationControlData *g_nsAppControlData = NULL; -static TitleApplicationMetadata **g_appMetadata = NULL; -static u32 g_appMetadataCount = 0; +static TitleApplicationMetadata **g_systemMetadata = NULL, **g_userMetadata = NULL; +static u32 g_systemMetadataCount = 0, g_userMetadataCount = 0; -static NcmContentMetaDatabase g_ncmDbGameCard = {0}, g_ncmDbEmmcSystem = {0}, g_ncmDbEmmcUser = {0}, g_ncmDbSdCard = {0}; -static NcmContentStorage g_ncmStorageGameCard = {0}, g_ncmStorageEmmcSystem = {0}, g_ncmStorageEmmcUser = {0}, g_ncmStorageSdCard = {0}; - -static TitleInfo **g_titleInfo = NULL; -static u32 g_titleInfoCount = 0; - -static u32 g_titleInfoBuiltInSystemCount = 0; /* Start index is always zero. */ -static u32 g_titleInfoBuiltInUserStartIndex = 0, g_titleInfoBuiltInUserCount = 0; -static u32 g_titleInfoSdCardStartIndex = 0, g_titleInfoSdCardCount = 0; -static u32 g_titleInfoGameCardStartIndex = 0, g_titleInfoGameCardCount = 0; +static TitleStorage g_titleStorage[TITLE_STORAGE_COUNT] = {0}; static TitleInfo **g_orphanTitleInfo = NULL; static u32 g_orphanTitleInfoCount = 0; @@ -392,32 +393,29 @@ static const u32 g_systemTitlesCount = MAX_ELEMENTS(g_systemTitles); /* Function prototypes. */ NX_INLINE void titleFreeApplicationMetadata(void); -static bool titleReallocateApplicationMetadata(u32 extra_app_count, bool free_entries); +static bool titleReallocateApplicationMetadata(u32 extra_app_count, bool is_system, bool free_entries); -NX_INLINE void titleFreeTitleInfo(void); -static bool titleReallocateTitleInfo(u32 extra_title_count, bool free_entries); +NX_INLINE bool titleInitializePersistentTitleStorages(void); +NX_INLINE void titleCloseTitleStorages(void); -NX_INLINE void titleFreeOrphanTitleInfo(void); +static bool titleInitializeTitleStorage(u8 storage_id); +static void titleCloseTitleStorage(u8 storage_id); +static bool titleReallocateTitleInfoFromStorage(TitleStorage *title_storage, u32 extra_title_count, bool free_entries); + +NX_INLINE void titleFreeOrphanTitleInfoEntries(void); static void titleAddOrphanTitleInfoEntry(TitleInfo *orphan_title); -NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id); - static bool titleGenerateMetadataEntriesFromSystemTitles(void); static bool titleGenerateMetadataEntriesFromNsRecords(void); -static bool titleRetrieveApplicationMetadataByTitleId(u64 title_id, TitleApplicationMetadata *out); +static TitleApplicationMetadata *titleGenerateDummySystemMetadataEntry(u64 title_id); +static bool titleRetrieveUserApplicationMetadataByTitleId(u64 title_id, TitleApplicationMetadata *out); -static bool titleOpenNcmDatabases(void); -static void titleCloseNcmDatabases(void); +NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id, bool is_system); -static bool titleOpenNcmStorages(void); -static void titleCloseNcmStorages(void); +static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_storage); +static bool titleGetMetaKeysFromContentDatabase(NcmContentMetaDatabase *ncm_db, NcmContentMetaKey **out_meta_keys, u32 *out_meta_key_count); +static bool titleGetContentInfosForMetaKey(NcmContentMetaDatabase *ncm_db, const NcmContentMetaKey *meta_key, NcmContentInfo **out_content_infos, u32 *out_content_count); -static bool titleOpenNcmDatabaseAndStorageFromGameCard(void); -static void titleCloseNcmDatabaseAndStorageFromGameCard(void); - -static bool titleLoadPersistentStorageTitleInfo(void); -static bool titleGenerateTitleInfoFromStorage(u8 storage_id); -static bool titleGetContentInfosFromTitle(u8 storage_id, const NcmContentMetaKey *meta_key, NcmContentInfo **out_content_infos, u32 *out_content_count); static void titleUpdateTitleInfoLinkedLists(void); static bool titleCreateGameCardInfoThread(void); @@ -425,11 +423,12 @@ static void titleDestroyGameCardInfoThread(void); static void titleGameCardInfoThreadFunc(void *arg); static bool titleRefreshGameCardTitleInfo(void); -static void titleRemoveGameCardTitleInfoEntries(void); static bool titleIsUserApplicationContentAvailable(u64 app_id); static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id); +static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *parent, TitleInfo *previous, TitleInfo *next); + static int titleSystemTitleMetadataEntrySortFunction(const void *a, const void *b); static int titleUserApplicationMetadataEntrySortFunction(const void *a, const void *b); static int titleOrphanTitleInfoSortFunction(const void *a, const void *b); @@ -468,24 +467,11 @@ bool titleInitialize(void) break; } - /* Open eMMC System, eMMC User and SD card ncm databases. */ - if (!titleOpenNcmDatabases()) + /* Initialize persistent title storages (BuiltInSystem, BuiltInUser, SdCard). */ + /* The background gamecard title thread will take care of initializing the gamecard title storage. */ + if (!titleInitializePersistentTitleStorages()) { - LOG_MSG("Failed to open ncm databases!"); - break; - } - - /* Open eMMC System, eMMC User and SD card ncm storages. */ - if (!titleOpenNcmStorages()) - { - LOG_MSG("Failed to open ncm storages!"); - break; - } - - /* Load title info by retrieving content meta keys from available eMMC System, eMMC User and SD card titles. */ - if (!titleLoadPersistentStorageTitleInfo()) - { - LOG_MSG("Failed to load persistent storage title info!"); + LOG_MSG("Failed to initialize persistent title storages!"); break; } @@ -500,9 +486,6 @@ bool titleInitialize(void) break; } - /* Create user-mode gamecard update info event. */ - ueventCreate(&g_titleGameCardUpdateInfoUserEvent, true); - /* Create gamecard title info thread. */ if (!(g_titleGameCardInfoThreadCreated = titleCreateGameCardInfoThread())) break; @@ -524,23 +507,21 @@ void titleExit(void) g_titleGameCardInfoThreadCreated = false; } - /* Free title info. */ - titleFreeTitleInfo(); + /* Close title storages. */ + titleCloseTitleStorages(); - /* Close gamecard ncm database and storage (if needed). */ - titleCloseNcmDatabaseAndStorageFromGameCard(); - - /* Close eMMC System, eMMC User and SD card ncm storages. */ - titleCloseNcmStorages(); - - /* Close eMMC System, eMMC User and SD card ncm databases. */ - titleCloseNcmDatabases(); + /* Free orphan title info entries. */ + titleFreeOrphanTitleInfoEntries(); /* Free application metadata. */ titleFreeApplicationMetadata(); /* Free ns application control data. */ - if (g_nsAppControlData) free(g_nsAppControlData); + if (g_nsAppControlData) + { + free(g_nsAppControlData); + g_nsAppControlData = NULL; + } g_titleInterfaceInit = false; } @@ -548,52 +529,14 @@ void titleExit(void) NcmContentMetaDatabase *titleGetNcmDatabaseByStorageId(u8 storage_id) { - NcmContentMetaDatabase *ncm_db = NULL; - - switch(storage_id) - { - case NcmStorageId_GameCard: - ncm_db = &g_ncmDbGameCard; - break; - case NcmStorageId_BuiltInSystem: - ncm_db = &g_ncmDbEmmcSystem; - break; - case NcmStorageId_BuiltInUser: - ncm_db = &g_ncmDbEmmcUser; - break; - case NcmStorageId_SdCard: - ncm_db = &g_ncmDbSdCard; - break; - default: - break; - } - - return ncm_db; + u8 idx = TITLE_STORAGE_INDEX(storage_id); + return (idx < TITLE_STORAGE_COUNT ? &(g_titleStorage[idx].ncm_db) : NULL); } NcmContentStorage *titleGetNcmStorageByStorageId(u8 storage_id) { - NcmContentStorage *ncm_storage = NULL; - - switch(storage_id) - { - case NcmStorageId_GameCard: - ncm_storage = &g_ncmStorageGameCard; - break; - case NcmStorageId_BuiltInSystem: - ncm_storage = &g_ncmStorageEmmcSystem; - break; - case NcmStorageId_BuiltInUser: - ncm_storage = &g_ncmStorageEmmcUser; - break; - case NcmStorageId_SdCard: - ncm_storage = &g_ncmStorageSdCard; - break; - default: - break; - } - - return ncm_storage; + u8 idx = TITLE_STORAGE_INDEX(storage_id); + return (idx < TITLE_STORAGE_COUNT ? &(g_titleStorage[idx].ncm_storage) : NULL); } TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count) @@ -603,19 +546,19 @@ TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u3 SCOPED_LOCK(&g_titleMutex) { - if (!g_titleInterfaceInit || !g_appMetadata || !*g_appMetadata || (is_system && g_appMetadataCount < g_systemTitlesCount) || (!is_system && g_appMetadataCount == g_systemTitlesCount) || \ - !out_count) + if (!g_titleInterfaceInit || (is_system && (!g_systemMetadata || !g_systemMetadataCount)) || (!is_system && (!g_userMetadata || !g_userMetadataCount)) || !out_count) { LOG_MSG("Invalid parameters!"); break; } - u32 start_idx = (is_system ? 0 : g_systemTitlesCount), max_val = (is_system ? g_systemTitlesCount : g_appMetadataCount); + TitleApplicationMetadata **cached_app_metadata = (is_system ? g_systemMetadata : g_userMetadata); + u32 cached_app_metadata_count = (is_system ? g_systemMetadataCount : g_userMetadataCount); bool error = false; - for(u32 i = start_idx; i < max_val; i++) + for(u32 i = 0; i < cached_app_metadata_count; i++) { - TitleApplicationMetadata *cur_app_metadata = g_appMetadata[i]; + TitleApplicationMetadata *cur_app_metadata = cached_app_metadata[i]; if (!cur_app_metadata) continue; /* Skip current metadata entry if content data for this title isn't available. */ @@ -654,50 +597,170 @@ TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u3 TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id) { TitleInfo *ret = NULL; - SCOPED_LOCK(&g_titleMutex) ret = _titleGetInfoFromStorageByTitleId(storage_id, title_id); + + SCOPED_LOCK(&g_titleMutex) + { + TitleInfo *title_info = (g_titleInterfaceInit ? _titleGetInfoFromStorageByTitleId(storage_id, title_id) : NULL); + if (title_info) + { + ret = titleDuplicateTitleInfo(title_info, NULL, NULL, NULL); + if (!ret) LOG_MSG("Failed to duplicate title info for %016lX!", title_id); + } + } + return ret; } +void titleFreeTitleInfo(TitleInfo **info) +{ + TitleInfo *ptr = NULL, *tmp1 = NULL, *tmp2 = NULL; + if (!info || !(ptr = *info)) return; + + /* Free content infos. */ + if (ptr->content_infos) free(ptr->content_infos); + + /* Free parent linked list. */ + titleFreeTitleInfo(&(ptr->parent)); + + /* Free previous sibling(s). */ + tmp1 = ptr->previous; + while(tmp1) + { + tmp2 = tmp1->previous; + tmp1->parent = tmp1->previous = tmp1->next = NULL; + titleFreeTitleInfo(&tmp1); + tmp1 = tmp2; + } + + /* Free next sibling(s). */ + tmp1 = ptr->next; + while(tmp1) + { + tmp2 = tmp1->next; + tmp1->parent = tmp1->previous = tmp1->next = NULL; + titleFreeTitleInfo(&tmp1); + tmp1 = tmp2; + } + + free(ptr); + *info = NULL; +} + bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out) { bool ret = false; SCOPED_LOCK(&g_titleMutex) { - if (!g_titleInterfaceInit || !g_titleInfo || !*g_titleInfo || !g_titleInfoCount || !app_id || !out) + if (!g_titleInterfaceInit || !app_id || !out) { LOG_MSG("Invalid parameters!"); break; } + TitleInfo *app_info = NULL, *patch_info = NULL, *aoc_info = NULL; + /* Clear output. */ - memset(out, 0, sizeof(TitleUserApplicationData)); + titleFreeUserApplicationData(out); /* Get info for the first user application title. */ - out->app_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id); + app_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id); + if (app_info) + { + out->app_info = titleDuplicateTitleInfo(app_info, NULL, NULL, NULL); + if (!out->app_info) + { + LOG_MSG("Failed to duplicate user application info for %016lX!", app_id); + break; + } + } /* Get info for the first patch title. */ - out->patch_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, titleGetPatchIdByApplicationId(app_id)); + patch_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, titleGetPatchIdByApplicationId(app_id)); + if (patch_info) + { + out->patch_info = titleDuplicateTitleInfo(patch_info, out->app_info, NULL, NULL); + if (!out->patch_info) + { + LOG_MSG("Failed to duplicate patch info for %016lX!", app_id); + break; + } + } /* Get info for the first add-on content title. */ - for(u32 i = g_titleInfoBuiltInUserStartIndex; i < g_titleInfoCount; i++) + for(u8 i = NcmStorageId_GameCard; i <= NcmStorageId_SdCard; i++) { - TitleInfo *title_info = g_titleInfo[i]; - if (title_info && title_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, title_info->meta_key.id)) + if (i == NcmStorageId_BuiltInSystem) continue; + + TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(i)]); + if (!title_storage->titles || !title_storage->title_count) continue; + + for(u32 j = 0; j < title_storage->title_count; j++) { - out->aoc_info = title_info; + TitleInfo *title_info = title_storage->titles[j]; + if (title_info && title_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, title_info->meta_key.id)) + { + aoc_info = title_info; + break; + } + } + + if (aoc_info) break; + } + + if (aoc_info) + { + out->aoc_info = titleDuplicateTitleInfo(aoc_info, out->app_info, NULL, NULL); + if (!out->aoc_info) + { + LOG_MSG("Failed to duplicate add-on content info for %016lX!", app_id); break; } } /* Check retrieved title info. */ - ret = (out->app_info || out->patch_info || out->aoc_info); + ret = (app_info || patch_info || aoc_info); if (!ret) LOG_MSG("Failed to retrieve user application data for ID \"%016lX\"!", app_id); } + /* Clear output. */ + if (!ret) titleFreeUserApplicationData(out); + return ret; } +void titleFreeUserApplicationData(TitleUserApplicationData *user_app_data) +{ + if (!user_app_data) return; + + TitleInfo *tmp = NULL; + + /* Free user application info. */ + titleFreeTitleInfo(&(user_app_data->app_info)); + + /* Make sure to clear all references to the parent linked list beforehand. */ + /* Unlike titleDuplicateTitleInfo(), we don't need to traverse backwards because elements from TitleUserApplicationData always point to the first title info. */ + tmp = user_app_data->patch_info; + while(tmp) + { + tmp->parent = NULL; + tmp = tmp->next; + } + + tmp = user_app_data->aoc_info; + while(tmp) + { + tmp->parent = NULL; + tmp = tmp->next; + } + + /* Free patch info. */ + titleFreeTitleInfo(&(user_app_data->patch_info)); + + /* Free add-on content info. */ + titleFreeTitleInfo(&(user_app_data->aoc_info)); +} + bool titleAreOrphanTitlesAvailable(void) { bool ret = false; @@ -705,36 +768,61 @@ bool titleAreOrphanTitlesAvailable(void) return ret; } -TitleInfo **titleGetInfoFromOrphanTitles(u32 *out_count) +TitleInfo **titleGetOrphanTitles(u32 *out_count) { TitleInfo **orphan_info = NULL; SCOPED_LOCK(&g_titleMutex) { - if (!g_titleInterfaceInit || !g_titleInfo || !*g_titleInfo || !g_titleInfoCount || !g_orphanTitleInfo || !*g_orphanTitleInfo || !g_orphanTitleInfoCount || !out_count) + if (!g_titleInterfaceInit || !g_orphanTitleInfo || !*g_orphanTitleInfo || !g_orphanTitleInfoCount || !out_count) { LOG_MSG("Invalid parameters!"); break; } /* Allocate orphan title info pointer array. */ - orphan_info = calloc(g_orphanTitleInfoCount, sizeof(TitleInfo*)); + orphan_info = calloc(g_orphanTitleInfoCount + 1, sizeof(TitleInfo*)); if (!orphan_info) { LOG_MSG("Failed to allocate memory for orphan title info pointer array!"); break; } - /* Get pointers to orphan title info entries. */ - for(u32 i = 0; i < g_orphanTitleInfoCount; i++) orphan_info[i] = g_orphanTitleInfo[i]; + /* Duplicate orphan title info entries. */ + for(u32 i = 0; i < g_orphanTitleInfoCount; i++) + { + orphan_info[i] = titleDuplicateTitleInfo(g_orphanTitleInfo[i], NULL, NULL, NULL); + if (!orphan_info[i]) + { + LOG_MSG("Failed to duplicate info for orphan title %016lX!", g_orphanTitleInfo[i]->meta_key.id); + titleFreeOrphanTitles(&orphan_info); + break; + } + } /* Update output counter. */ - *out_count = g_orphanTitleInfoCount; + if (orphan_info) *out_count = g_orphanTitleInfoCount; } return orphan_info; } +void titleFreeOrphanTitles(TitleInfo ***orphan_info) +{ + TitleInfo **ptr = NULL, *tmp = NULL; + if (!orphan_info || !(ptr = *orphan_info)) return; + + tmp = *ptr; + while(tmp) + { + titleFreeTitleInfo(&tmp); + tmp++; + } + + free(ptr); + *orphan_info = NULL; +} + bool titleIsGameCardInfoUpdated(void) { bool ret = false; @@ -742,17 +830,7 @@ bool titleIsGameCardInfoUpdated(void) SCOPED_LOCK(&g_titleMutex) { /* Check if the gamecard thread detected a gamecard status change. */ - ret = (g_titleInterfaceInit && g_titleGameCardInfoThreadCreated && g_titleGameCardInfoUpdated); - if (!ret) break; - - /* Signal the gamecard update info user event. */ - ueventSignal(&g_titleGameCardUpdateInfoUserEvent); - - /* Wait for the gamecard thread to wake us up. */ - condvarWait(&g_gameCardCondVar, &g_titleMutex); - - /* Update output value and gamecard info updated flag (if needed). */ - ret = g_titleGameCardInfoUpdated; + ret = (g_titleInterfaceInit && g_titleGameCardInfoUpdated); if (ret) g_titleGameCardInfoUpdated = false; } @@ -768,34 +846,29 @@ char *titleGenerateFileName(const TitleInfo *title_info, u8 name_convention, u8 return NULL; } - char *filename = NULL; + char title_name[0x400] = {0}, *filename = NULL; + u8 type = (title_info->meta_key.type - 0x80); - SCOPED_LOCK(&g_titleMutex) + /* Generate filename for this title. */ + if (name_convention == TitleFileNameConvention_Full) { - char title_name[0x400] = {0}; - u8 type = (title_info->meta_key.type - 0x80); - - /* Generate filename for this title. */ - if (name_convention == TitleFileNameConvention_Full) + if (title_info->app_metadata && *(title_info->app_metadata->lang_entry.name)) { - if (title_info->app_metadata && *(title_info->app_metadata->lang_entry.name)) - { - sprintf(title_name, "%s ", title_info->app_metadata->lang_entry.name); - if (illegal_char_replace_type) utilsReplaceIllegalCharacters(title_name, illegal_char_replace_type == TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly); - } - - sprintf(title_name + strlen(title_name), "[%016lX][v%u][%s]", title_info->meta_key.id, title_info->meta_key.version, g_filenameTypeStrings[type]); - } else - if (name_convention == TitleFileNameConvention_IdAndVersionOnly) - { - sprintf(title_name, "%016lX_v%u_%s", title_info->meta_key.id, title_info->meta_key.version, g_filenameTypeStrings[type]); + sprintf(title_name, "%s ", title_info->app_metadata->lang_entry.name); + if (illegal_char_replace_type) utilsReplaceIllegalCharacters(title_name, illegal_char_replace_type == TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly); } - /* Duplicate generated filename. */ - filename = strdup(title_name); - if (!filename) LOG_MSG("Failed to duplicate generated filename!"); + sprintf(title_name + strlen(title_name), "[%016lX][v%u][%s]", title_info->meta_key.id, title_info->meta_key.version, g_filenameTypeStrings[type]); + } else + if (name_convention == TitleFileNameConvention_IdAndVersionOnly) + { + sprintf(title_name, "%016lX_v%u_%s", title_info->meta_key.id, title_info->meta_key.version, g_filenameTypeStrings[type]); } + /* Duplicate generated filename. */ + filename = strdup(title_name); + if (!filename) LOG_MSG("Failed to duplicate generated filename!"); + return filename; } @@ -805,32 +878,36 @@ char *titleGenerateGameCardFileName(u8 name_convention, u8 illegal_char_replace_ SCOPED_LOCK(&g_titleMutex) { - if (!g_titleInterfaceInit || !g_titleInfo || !*g_titleInfo || !g_titleInfoCount || !g_titleGameCardAvailable || !g_titleInfoGameCardCount || \ - name_convention > TitleFileNameConvention_IdAndVersionOnly || (name_convention == TitleFileNameConvention_Full && \ - illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly)) + TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(NcmStorageId_GameCard)]); + TitleInfo **titles = title_storage->titles; + u32 title_count = title_storage->title_count; + + if (!g_titleInterfaceInit || !g_titleGameCardAvailable || !titles || !title_count || name_convention > TitleFileNameConvention_IdAndVersionOnly || \ + (name_convention == TitleFileNameConvention_Full && illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly)) { LOG_MSG("Invalid parameters!"); break; } + GameCardHeader gc_header = {0}; size_t cur_filename_len = 0; - char *tmp_filename = NULL, app_name[0x400] = {0}; + char app_name[0x400] = {0}, *tmp_filename = NULL; bool error = false; - for(u32 i = g_titleInfoGameCardStartIndex; i < g_titleInfoCount; i++) + for(u32 i = 0; i < title_count; i++) { - TitleInfo *app_info = g_titleInfo[i]; + TitleInfo *app_info = titles[i]; if (!app_info || app_info->meta_key.type != NcmContentMetaType_Application) continue; u32 app_version = app_info->meta_key.version; /* Check if the inserted gamecard holds any bundled patches for the current user application. */ /* If so, we'll use the highest patch version available as part of the filename. */ - for(u32 j = g_titleInfoGameCardStartIndex; j < g_titleInfoCount; j++) + for(u32 j = 0; j < title_count; j++) { if (j == i) continue; - TitleInfo *patch_info = g_titleInfo[j]; + TitleInfo *patch_info = titles[j]; if (!patch_info || patch_info->meta_key.type != NcmContentMetaType_Patch || !titleCheckIfPatchIdBelongsToApplicationId(app_info->meta_key.id, patch_info->meta_key.id) || \ patch_info->meta_key.version <= app_version) continue; @@ -880,13 +957,25 @@ char *titleGenerateGameCardFileName(u8 name_convention, u8 illegal_char_replace_ cur_filename_len += app_name_len; } - if (!filename && !error) LOG_MSG("Error: the inserted gamecard doesn't hold any user applications!"); + if (!filename && !error) + { + LOG_MSG("Error: the inserted gamecard doesn't hold any user applications!"); + + /* Fallback string if no applications can be found. */ + /* This function is guaranteed to fail with Kiosk / Quest gamecards, so that's why this is needed. */ + sprintf(app_name, "gamecard"); + + if (gamecardGetHeader(&gc_header)) + { + strcat(app_name, "_"); + cur_filename_len = strlen(app_name); + utilsGenerateHexStringFromData(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); + } } - /* Fallback string if any errors occur. */ - /* This function is guaranteed to fail with Kiosk / Quest gamecards, so that's why this is needed. */ - if (!filename) filename = strdup("gamecard"); - return filename; } @@ -903,11 +992,11 @@ const char *titleGetNcmContentMetaTypeName(u8 content_meta_type) NX_INLINE void titleFreeApplicationMetadata(void) { - if (g_appMetadata) + if (g_systemMetadata) { - for(u32 i = 0; i < g_appMetadataCount; i++) + for(u32 i = 0; i < g_systemMetadataCount; i++) { - TitleApplicationMetadata *cur_app_metadata = g_appMetadata[i]; + TitleApplicationMetadata *cur_app_metadata = g_systemMetadata[i]; if (cur_app_metadata) { if (cur_app_metadata->icon) free(cur_app_metadata->icon); @@ -915,31 +1004,50 @@ NX_INLINE void titleFreeApplicationMetadata(void) } } - free(g_appMetadata); - g_appMetadata = NULL; + free(g_systemMetadata); + g_systemMetadata = NULL; } - g_appMetadataCount = 0; + g_systemMetadataCount = 0; + + if (g_userMetadata) + { + for(u32 i = 0; i < g_userMetadataCount; i++) + { + TitleApplicationMetadata *cur_app_metadata = g_userMetadata[i]; + if (cur_app_metadata) + { + if (cur_app_metadata->icon) free(cur_app_metadata->icon); + free(cur_app_metadata); + } + } + + free(g_userMetadata); + g_userMetadata = NULL; + } + + g_userMetadataCount = 0; } -static bool titleReallocateApplicationMetadata(u32 extra_app_count, bool free_entries) +static bool titleReallocateApplicationMetadata(u32 extra_app_count, bool is_system, bool free_entries) { - if (free_entries && !g_appMetadata) - { - LOG_MSG("Invalid parameters!"); - return false; - } - - TitleApplicationMetadata **tmp_app_metadata = NULL; - u32 realloc_app_count = (!free_entries ? (g_appMetadataCount + extra_app_count) : g_appMetadataCount); + TitleApplicationMetadata **cached_app_metadata = (is_system ? g_systemMetadata : g_userMetadata), **tmp_app_metadata = NULL; + u32 cached_app_metadata_count = (is_system ? g_systemMetadataCount : g_userMetadataCount); + u32 realloc_app_count = (!free_entries ? (cached_app_metadata_count + extra_app_count) : cached_app_metadata_count); bool success = false; if (free_entries) { + if (!cached_app_metadata) + { + LOG_MSG("Invalid parameters!"); + goto end; + } + /* Free previously allocated application metadata entries. */ for(u32 i = 0; i <= extra_app_count; i++) { - TitleApplicationMetadata *cur_app_metadata = g_appMetadata[g_appMetadataCount + i]; + TitleApplicationMetadata *cur_app_metadata = cached_app_metadata[cached_app_metadata_count + i]; if (cur_app_metadata) { if (cur_app_metadata->icon) free(cur_app_metadata->icon); @@ -952,23 +1060,33 @@ static bool titleReallocateApplicationMetadata(u32 extra_app_count, bool free_en if (realloc_app_count) { /* Reallocate application metadata pointer array. */ - tmp_app_metadata = realloc(g_appMetadata, realloc_app_count * sizeof(TitleApplicationMetadata*)); + tmp_app_metadata = realloc(cached_app_metadata, realloc_app_count * sizeof(TitleApplicationMetadata*)); if (tmp_app_metadata) { /* Update application metadata pointer. */ - g_appMetadata = tmp_app_metadata; + cached_app_metadata = tmp_app_metadata; tmp_app_metadata = NULL; /* Clear new application metadata pointer array area (if needed). */ - if (!free_entries && extra_app_count) memset(g_appMetadata + g_appMetadataCount, 0, extra_app_count * sizeof(TitleApplicationMetadata*)); + if (!free_entries && extra_app_count) memset(cached_app_metadata + cached_app_metadata_count, 0, extra_app_count * sizeof(TitleApplicationMetadata*)); } else { LOG_MSG("Failed to reallocate application metadata pointer array! (%u element[s]).", realloc_app_count); goto end; } - } else { + } else + if (cached_app_metadata) + { /* Free application metadata pointer array. */ - free(g_appMetadata); - g_appMetadata = NULL; + free(cached_app_metadata); + cached_app_metadata = NULL; + } + + /* Update global pointer array. */ + if (is_system) + { + g_systemMetadata = cached_app_metadata; + } else { + g_userMetadata = cached_app_metadata; } /* Update flag. */ @@ -978,13 +1096,98 @@ end: return success; } -NX_INLINE void titleFreeTitleInfo(void) +NX_INLINE bool titleInitializePersistentTitleStorages(void) { - if (g_titleInfo) + for(u8 i = NcmStorageId_BuiltInSystem; i <= NcmStorageId_SdCard; i++) { - for(u32 i = 0; i < g_titleInfoCount; i++) + if (!titleInitializeTitleStorage(i)) { - TitleInfo *cur_title_info = g_titleInfo[i]; + LOG_MSG("Failed to initialize title storage with ID %u!", i); + return false; + } + } + + return true; +} + +NX_INLINE void titleCloseTitleStorages(void) +{ + for(u8 i = NcmStorageId_GameCard; i <= NcmStorageId_SdCard; i++) titleCloseTitleStorage(i); +} + +static bool titleInitializeTitleStorage(u8 storage_id) +{ + if (storage_id < NcmStorageId_GameCard || storage_id > NcmStorageId_SdCard) + { + LOG_MSG("Invalid parameters!"); + return false; + } + + /* Close title storage before proceeding. */ + titleCloseTitleStorage(storage_id); + + TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(storage_id)]); + NcmContentMetaDatabase *ncm_db = &(title_storage->ncm_db); + NcmContentStorage *ncm_storage = &(title_storage->ncm_storage); + + Result rc = 0; + bool success = false; + + /* Set ncm storage ID. */ + title_storage->storage_id = storage_id; + + /* Open ncm database. */ + rc = ncmOpenContentMetaDatabase(ncm_db, storage_id); + if (R_FAILED(rc)) + { + /* If the SD card is mounted, but it isn't currently being used by HOS, 0x21005 will be returned, so we'll just filter this particular error and continue. */ + /* This can occur when using the "Nintendo" directory from a different console, or when the "sdmc:/Nintendo/Contents/private" file is corrupted. */ + LOG_MSG("ncmOpenContentMetaDatabase failed for storage ID %u! (0x%08X).", storage_id, rc); + if (storage_id == NcmStorageId_SdCard && rc == 0x21005) success = true; + goto end; + } + + /* Open ncm storage. */ + rc = ncmOpenContentStorage(ncm_storage, storage_id); + if (R_FAILED(rc)) + { + /* If the SD card is mounted, but it isn't currently being used by HOS, 0x21005 will be returned, so we'll just filter this particular error and continue. */ + /* This can occur when using the "Nintendo" directory from a different console, or when the "sdmc:/Nintendo/Contents/private" file is corrupted. */ + LOG_MSG("ncmOpenContentStorage failed for storage ID %u! (0x%08X).", storage_id, rc); + if (storage_id == NcmStorageId_SdCard && rc == 0x21005) success = true; + goto end; + } + + /* Generate title info entries for this storage. */ + if (!titleGenerateTitleInfoEntriesForTitleStorage(title_storage)) + { + LOG_MSG("Failed to generate title info entries for storage ID %u!", storage_id); + goto end; + } + + LOG_MSG("Loaded %u title info %s from storage ID %u.", title_storage->title_count, (title_storage->title_count == 1 ? "entry" : "entries"), storage_id); + + /* Update flag. */ + success = true; + +end: + return success; +} + +static void titleCloseTitleStorage(u8 storage_id) +{ + if (storage_id < NcmStorageId_GameCard || storage_id > NcmStorageId_SdCard) return; + + TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(storage_id)]); + NcmContentMetaDatabase *ncm_db = &(title_storage->ncm_db); + NcmContentStorage *ncm_storage = &(title_storage->ncm_storage); + + /* Free title infos from this title storage. */ + if (title_storage->titles) + { + for(u32 i = 0; i < title_storage->title_count; i++) + { + TitleInfo *cur_title_info = title_storage->titles[i]; if (cur_title_info) { if (cur_title_info->content_infos) free(cur_title_info->content_infos); @@ -992,36 +1195,48 @@ NX_INLINE void titleFreeTitleInfo(void) } } - free(g_titleInfo); - g_titleInfo = NULL; + free(title_storage->titles); + title_storage->titles = NULL; } - titleFreeOrphanTitleInfo(); + /* Reset title count. */ + title_storage->title_count = 0; - g_titleInfoCount = g_titleInfoBuiltInSystemCount = 0; - g_titleInfoBuiltInUserStartIndex = g_titleInfoBuiltInUserCount = 0; - g_titleInfoSdCardStartIndex = g_titleInfoSdCardCount = 0; - g_titleInfoGameCardStartIndex = g_titleInfoGameCardCount = 0; + /* Check if the ncm storage handle for this title storage has already been retrieved. If so, close it. */ + if (serviceIsActive(&(ncm_storage->s))) ncmContentStorageClose(ncm_storage); + + /* Check if the ncm database handle for this title storage has already been retrieved. If so, close it. */ + if (serviceIsActive(&(ncm_db->s))) ncmContentMetaDatabaseClose(ncm_db); + + /* Reset ncm storage ID. */ + title_storage->storage_id = NcmStorageId_None; } -static bool titleReallocateTitleInfo(u32 extra_title_count, bool free_entries) +static bool titleReallocateTitleInfoFromStorage(TitleStorage *title_storage, u32 extra_title_count, bool free_entries) { - if (free_entries && !g_titleInfo) + if (!title_storage) { LOG_MSG("Invalid parameters!"); return false; } - TitleInfo **tmp_title_info = NULL; - u32 realloc_title_count = (!free_entries ? (g_titleInfoCount + extra_title_count) : g_titleInfoCount); + TitleInfo **title_info = title_storage->titles, **tmp_title_info = NULL; + u32 title_count = title_storage->title_count; + u32 realloc_title_count = (!free_entries ? (title_count + extra_title_count) : title_count); bool success = false; if (free_entries) { + if (!title_info) + { + LOG_MSG("Invalid parameters!"); + goto end; + } + /* Free previously allocated title info entries. */ for(u32 i = 0; i <= extra_title_count; i++) { - TitleInfo *cur_title_info = g_titleInfo[g_titleInfoCount + i]; + TitleInfo *cur_title_info = title_info[title_count + i]; if (cur_title_info) { if (cur_title_info->content_infos) free(cur_title_info->content_infos); @@ -1034,25 +1249,30 @@ static bool titleReallocateTitleInfo(u32 extra_title_count, bool free_entries) if (realloc_title_count) { /* Reallocate title info pointer array. */ - tmp_title_info = realloc(g_titleInfo, realloc_title_count * sizeof(TitleInfo*)); + tmp_title_info = realloc(title_info, realloc_title_count * sizeof(TitleInfo*)); if (tmp_title_info) { /* Update title info pointer. */ - g_titleInfo = tmp_title_info; + title_info = tmp_title_info; tmp_title_info = NULL; /* Clear new title info pointer array area (if needed). */ - if (!free_entries && extra_title_count) memset(g_titleInfo + g_titleInfoCount, 0, extra_title_count * sizeof(TitleInfo*)); + if (!free_entries && extra_title_count) memset(title_info + title_count, 0, extra_title_count * sizeof(TitleInfo*)); } else { LOG_MSG("Failed to reallocate title info pointer array! (%u element[s]).", realloc_title_count); goto end; } - } else { + } else + if (title_info) + { /* Free title info pointer array. */ - free(g_titleInfo); - g_titleInfo = NULL; + free(title_info); + title_info = NULL; } + /* Update pointer array in global title storage. */ + title_storage->titles = title_info; + /* Update flag. */ success = true; @@ -1060,7 +1280,7 @@ end: return success; } -NX_INLINE void titleFreeOrphanTitleInfo(void) +NX_INLINE void titleFreeOrphanTitleInfoEntries(void) { if (g_orphanTitleInfo) { @@ -1093,26 +1313,13 @@ static void titleAddOrphanTitleInfoEntry(TitleInfo *orphan_title) if (g_orphanTitleInfoCount > 1) qsort(g_orphanTitleInfo, g_orphanTitleInfoCount, sizeof(TitleInfo*), &titleOrphanTitleInfoSortFunction); } -NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id) -{ - if (!g_appMetadata || !*g_appMetadata || !g_appMetadataCount || !title_id) return NULL; - - for(u32 i = 0; i < g_appMetadataCount; i++) - { - TitleApplicationMetadata *cur_app_metadata = g_appMetadata[i]; - if (cur_app_metadata && cur_app_metadata->title_id == title_id) return cur_app_metadata; - } - - return NULL; -} - static bool titleGenerateMetadataEntriesFromSystemTitles(void) { u32 extra_app_count = 0; bool success = false; /* Reallocate application metadata pointer array. */ - if (!titleReallocateApplicationMetadata(g_systemTitlesCount, false)) + if (!titleReallocateApplicationMetadata(g_systemTitlesCount, true, false)) { LOG_MSG("Failed to reallocate application metadata pointer array for system titles!"); return false; @@ -1135,21 +1342,21 @@ static bool titleGenerateMetadataEntriesFromSystemTitles(void) sprintf(cur_app_metadata->lang_entry.name, system_title->name); /* Set application metadata entry pointer. */ - g_appMetadata[g_appMetadataCount + extra_app_count] = cur_app_metadata; + g_systemMetadata[g_systemMetadataCount + extra_app_count] = cur_app_metadata; } - /* Sort metadata entries by title ID. */ - if (g_systemTitlesCount > 1) qsort(g_appMetadata + g_appMetadataCount, g_systemTitlesCount, sizeof(TitleApplicationMetadata*), &titleSystemTitleMetadataEntrySortFunction); - /* Update application metadata count. */ - g_appMetadataCount += g_systemTitlesCount; + g_systemMetadataCount += g_systemTitlesCount; + + /* Sort metadata entries by title ID. */ + if (g_systemMetadataCount > 1) qsort(g_systemMetadata, g_systemMetadataCount, sizeof(TitleApplicationMetadata*), &titleSystemTitleMetadataEntrySortFunction); /* Update flag. */ success = true; end: /* Free previously allocated application metadata pointers. Ignore return value. */ - if (!success) titleReallocateApplicationMetadata(extra_app_count, true); + if (!success) titleReallocateApplicationMetadata(extra_app_count, true, true); return success; } @@ -1187,7 +1394,7 @@ static bool titleGenerateMetadataEntriesFromNsRecords(void) } /* Reallocate application metadata pointer array. */ - if (!titleReallocateApplicationMetadata(app_records_count, false)) + if (!titleReallocateApplicationMetadata(app_records_count, false, false)) { LOG_MSG("Failed to reallocate application metadata pointer array for NS records!"); goto end; @@ -1198,7 +1405,7 @@ static bool titleGenerateMetadataEntriesFromNsRecords(void) /* Retrieve application metadata for each NS application record. */ for(u32 i = 0; i < app_records_count; i++) { - TitleApplicationMetadata *cur_app_metadata = g_appMetadata[g_appMetadataCount + extra_app_count]; + TitleApplicationMetadata *cur_app_metadata = g_userMetadata[g_userMetadataCount + extra_app_count]; if (!cur_app_metadata) { /* Allocate memory for a new application metadata entry. */ @@ -1210,11 +1417,11 @@ static bool titleGenerateMetadataEntriesFromNsRecords(void) } /* Set application metadata entry pointer. */ - g_appMetadata[g_appMetadataCount + extra_app_count] = cur_app_metadata; + g_userMetadata[g_userMetadataCount + extra_app_count] = cur_app_metadata; } /* Retrieve application metadata. */ - if (!titleRetrieveApplicationMetadataByTitleId(app_records[i].application_id, cur_app_metadata)) continue; + if (!titleRetrieveUserApplicationMetadataByTitleId(app_records[i].application_id, cur_app_metadata)) continue; /* Increase extra application metadata counter. */ extra_app_count++; @@ -1227,14 +1434,14 @@ static bool titleGenerateMetadataEntriesFromNsRecords(void) goto end; } - /* Sort application metadata entries by name. */ - if (extra_app_count > 1) qsort(g_appMetadata + g_appMetadataCount, extra_app_count, sizeof(TitleApplicationMetadata*), &titleUserApplicationMetadataEntrySortFunction); - /* Update application metadata count. */ - g_appMetadataCount += extra_app_count; + g_userMetadataCount += extra_app_count; /* Free extra allocated pointers if we didn't use them. */ - if (extra_app_count < app_records_count) titleReallocateApplicationMetadata(0, false); + if (extra_app_count < app_records_count) titleReallocateApplicationMetadata(0, false, false); + + /* Sort application metadata entries by name. */ + if (g_userMetadataCount > 1) qsort(g_userMetadata, g_userMetadataCount, sizeof(TitleApplicationMetadata*), &titleUserApplicationMetadataEntrySortFunction); /* Update flag. */ success = true; @@ -1243,12 +1450,57 @@ end: if (app_records) free(app_records); /* Free previously allocated application metadata pointers. Ignore return value. */ - if (!success && free_entries) titleReallocateApplicationMetadata(extra_app_count, true); + if (!success && free_entries) titleReallocateApplicationMetadata(extra_app_count, false, true); return success; } -static bool titleRetrieveApplicationMetadataByTitleId(u64 title_id, TitleApplicationMetadata *out) +static TitleApplicationMetadata *titleGenerateDummySystemMetadataEntry(u64 title_id) +{ + if (!title_id) + { + LOG_MSG("Invalid parameters!"); + return NULL; + } + + TitleApplicationMetadata *cur_app_metadata = NULL; + bool free_entry = false; + + /* Reallocate application metadata pointer array. */ + if (!titleReallocateApplicationMetadata(1, true, false)) + { + LOG_MSG("Failed to reallocate application metadata pointer array for %016lX!", title_id); + goto end; + } + + free_entry = true; + + /* Allocate memory for the current entry. */ + cur_app_metadata = calloc(1, sizeof(TitleApplicationMetadata)); + if (!cur_app_metadata) + { + LOG_MSG("Failed to allocate memory for application metadata %016lX!", title_id); + goto end; + } + + /* Fill information. */ + cur_app_metadata->title_id = title_id; + sprintf(cur_app_metadata->lang_entry.name, "Unknown"); + + /* Set application metadata entry pointer. */ + g_systemMetadata[g_systemMetadataCount++] = cur_app_metadata; + + /* Sort metadata entries by title ID. */ + if (g_systemMetadataCount > 1) qsort(g_systemMetadata, g_systemMetadataCount, sizeof(TitleApplicationMetadata*), &titleSystemTitleMetadataEntrySortFunction); + +end: + /* Free previously allocated application metadata pointer. Ignore return value. */ + if (!cur_app_metadata && free_entry) titleReallocateApplicationMetadata(0, true, false); + + return cur_app_metadata; +} + +static bool titleRetrieveUserApplicationMetadataByTitleId(u64 title_id, TitleApplicationMetadata *out) { if (!g_nsAppControlData || !title_id || !out) { @@ -1312,200 +1564,153 @@ static bool titleRetrieveApplicationMetadataByTitleId(u64 title_id, TitleApplica return true; } -static bool titleOpenNcmDatabases(void) +NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id, bool is_system) { - Result rc = 0; - NcmContentMetaDatabase *ncm_db = NULL; + if ((is_system && (!g_systemMetadata || !g_systemMetadataCount)) || (!is_system && (!g_userMetadata || !g_userMetadataCount)) || !title_id) return NULL; - for(u8 i = NcmStorageId_BuiltInSystem; i <= NcmStorageId_SdCard; i++) + TitleApplicationMetadata **cached_app_metadata = (is_system ? g_systemMetadata : g_userMetadata); + u32 cached_app_metadata_count = (is_system ? g_systemMetadataCount : g_userMetadataCount); + + for(u32 i = 0; i < cached_app_metadata_count; i++) { - /* Retrieve ncm database pointer. */ - ncm_db = titleGetNcmDatabaseByStorageId(i); - if (!ncm_db) - { - LOG_MSG("Failed to retrieve ncm database pointer for storage ID %u!", i); - return false; - } - - /* Check if the ncm database handle has already been retrieved. */ - if (serviceIsActive(&(ncm_db->s))) continue; - - /* Open ncm database. */ - rc = ncmOpenContentMetaDatabase(ncm_db, i); - if (R_FAILED(rc)) - { - /* If the SD card is mounted, but it isn't currently being used by HOS, 0x21005 will be returned, so we'll just filter this particular error and continue. */ - /* This can occur when using the "Nintendo" directory from a different console, or when the "sdmc:/Nintendo/Contents/private" file is corrupted. */ - LOG_MSG("ncmOpenContentMetaDatabase failed for storage ID %u! (0x%08X).", i, rc); - if (i == NcmStorageId_SdCard && rc == 0x21005) continue; - return false; - } + TitleApplicationMetadata *cur_app_metadata = cached_app_metadata[i]; + if (cur_app_metadata && cur_app_metadata->title_id == title_id) return cur_app_metadata; } - return true; + return NULL; } -static void titleCloseNcmDatabases(void) +static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_storage) { - NcmContentMetaDatabase *ncm_db = NULL; - - for(u8 i = NcmStorageId_BuiltInSystem; i <= NcmStorageId_SdCard; i++) + if (!title_storage || title_storage->storage_id < NcmStorageId_GameCard || title_storage->storage_id > NcmStorageId_SdCard || !serviceIsActive(&(title_storage->ncm_db.s))) { - /* 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))) ncmContentMetaDatabaseClose(ncm_db); - } -} - -static bool titleOpenNcmStorages(void) -{ - Result rc = 0; - NcmContentStorage *ncm_storage = NULL; - - for(u8 i = NcmStorageId_BuiltInSystem; i <= NcmStorageId_SdCard; i++) - { - /* Retrieve ncm storage pointer. */ - ncm_storage = titleGetNcmStorageByStorageId(i); - if (!ncm_storage) - { - LOG_MSG("Failed to retrieve ncm storage pointer for storage ID %u!", i); - return false; - } - - /* Check if the ncm storage handle has already been retrieved. */ - if (serviceIsActive(&(ncm_storage->s))) continue; - - /* Open ncm storage. */ - rc = ncmOpenContentStorage(ncm_storage, i); - if (R_FAILED(rc)) - { - /* If the SD card is mounted, but it isn't currently being used by HOS, 0x21005 will be returned, so we'll just filter this particular error and continue. */ - /* This can occur when using the "Nintendo" directory from a different console, or when the "sdmc:/Nintendo/Contents/private" file is corrupted. */ - LOG_MSG("ncmOpenContentStorage failed for storage ID %u! (0x%08X).", i, rc); - if (i == NcmStorageId_SdCard && rc == 0x21005) continue; - return false; - } + LOG_MSG("Invalid parameters!"); + return false; } - return true; -} - -static void titleCloseNcmStorages(void) -{ - NcmContentStorage *ncm_storage = NULL; + u8 storage_id = title_storage->storage_id; + NcmContentMetaDatabase *ncm_db = &(title_storage->ncm_db); - for(u8 i = NcmStorageId_BuiltInSystem; i <= NcmStorageId_SdCard; i++) - { - /* Retrieve ncm storage pointer. */ - ncm_storage = titleGetNcmStorageByStorageId(i); - if (!ncm_storage) continue; - - /* Check if the ncm storage handle has already been retrieved. */ - if (serviceIsActive(&(ncm_storage->s))) ncmContentStorageClose(ncm_storage); - } -} - -static bool titleOpenNcmDatabaseAndStorageFromGameCard(void) -{ - Result rc = 0; - NcmContentMetaDatabase *ncm_db = &g_ncmDbGameCard; - NcmContentStorage *ncm_storage = &g_ncmStorageGameCard; + u32 total = 0, extra_title_count = 0; + NcmContentMetaKey *meta_keys = NULL; - /* Open ncm database. */ - rc = ncmOpenContentMetaDatabase(ncm_db, NcmStorageId_GameCard); - if (R_FAILED(rc)) + bool success = false, free_entries = false; + + /* Get content meta keys for this storage. */ + if (!titleGetMetaKeysFromContentDatabase(ncm_db, &meta_keys, &total)) goto end; + + /* Check if we're dealing with an empty storage. */ + if (!total) { - LOG_MSG("ncmOpenContentMetaDatabase failed! (0x%08X).", rc); + success = true; goto end; } - /* Open ncm storage. */ - rc = ncmOpenContentStorage(ncm_storage, NcmStorageId_GameCard); - if (R_FAILED(rc)) + /* Reallocate pointer array in title storage. */ + if (!titleReallocateTitleInfoFromStorage(title_storage, total, false)) goto end; + + free_entries = true; + + /* Fill new title info entries. */ + for(u32 i = 0; i < total; i++) { - LOG_MSG("ncmOpenContentStorage failed! (0x%08X).", rc); + u64 tmp_size = 0; + NcmContentMetaKey *cur_meta_key = &(meta_keys[i]); + + TitleInfo *cur_title_info = title_storage->titles[title_storage->title_count + extra_title_count]; + if (!cur_title_info) + { + /* Allocate memory for a new entry. */ + cur_title_info = calloc(1, sizeof(TitleInfo)); + if (!cur_title_info) + { + LOG_MSG("Failed to allocate memory for title info entry #%u! (%u / %u).", extra_title_count, i + 1, total); + goto end; + } + + /* Set title info entry pointer. */ + title_storage->titles[title_storage->title_count + extra_title_count] = cur_title_info; + } + + /* Get content infos. */ + if (!titleGetContentInfosForMetaKey(ncm_db, cur_meta_key, &(cur_title_info->content_infos), &(cur_title_info->content_count))) + { + LOG_MSG("Failed to get content infos for title ID %016lX!", cur_meta_key->id); + continue; + } + + /* Calculate title size. */ + for(u32 j = 0; j < cur_title_info->content_count; j++) + { + titleConvertNcmContentSizeToU64(cur_title_info->content_infos[j].size, &tmp_size); + cur_title_info->size += tmp_size; + } + + /* Fill information. */ + cur_title_info->storage_id = storage_id; + memcpy(&(cur_title_info->meta_key), cur_meta_key, sizeof(NcmContentMetaKey)); + cur_title_info->version.value = cur_title_info->meta_key.version; + utilsGenerateFormattedSizeString(cur_title_info->size, cur_title_info->size_str, sizeof(cur_title_info->size_str)); + + /* Retrieve application metadata. */ + u64 app_id = (cur_title_info->meta_key.type <= NcmContentMetaType_Application ? cur_title_info->meta_key.id : \ + (cur_title_info->meta_key.type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(cur_title_info->meta_key.id) : \ + (cur_title_info->meta_key.type == NcmContentMetaType_AddOnContent ? titleGetApplicationIdByAddOnContentId(cur_title_info->meta_key.id) : \ + titleGetApplicationIdByDeltaId(cur_title_info->meta_key.id)))); + + cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, storage_id == NcmStorageId_BuiltInSystem); + if (!cur_title_info->app_metadata && storage_id == NcmStorageId_BuiltInSystem) + { + /* Generate dummy system metadata entry if we have no hardcoded information for this system title. */ + cur_title_info->app_metadata = titleGenerateDummySystemMetadataEntry(cur_title_info->meta_key.id); + } + + /* Increase extra title info counter. */ + extra_title_count++; + } + + /* Check retrieved title info count. */ + if (!extra_title_count) + { + LOG_MSG("Unable to generate title info entries! (%u element[s]).", total); goto end; } + /* Update title info count. */ + title_storage->title_count += extra_title_count; + + /* Free extra allocated pointers if we didn't use them. */ + if (extra_title_count < total) titleReallocateTitleInfoFromStorage(title_storage, 0, false); + + /* Update linked lists for user applications, patches and add-on contents. */ + /* This will also keep track of orphan titles - titles with no available application metadata. */ + titleUpdateTitleInfoLinkedLists(); + + /* Update flag. */ + success = true; + end: - return R_SUCCEEDED(rc); + if (meta_keys) free(meta_keys); + + /* Free previously allocated title info pointers. Ignore return value. */ + if (!success && free_entries) titleReallocateTitleInfoFromStorage(title_storage, extra_title_count, true); + + return success; } -static void titleCloseNcmDatabaseAndStorageFromGameCard(void) +static bool titleGetMetaKeysFromContentDatabase(NcmContentMetaDatabase *ncm_db, NcmContentMetaKey **out_meta_keys, u32 *out_meta_key_count) { - NcmContentMetaDatabase *ncm_db = &g_ncmDbGameCard; - NcmContentStorage *ncm_storage = &g_ncmStorageGameCard; - - /* Check if the ncm database handle has already been retrieved. */ - if (serviceIsActive(&(ncm_db->s))) ncmContentMetaDatabaseClose(ncm_db); - - /* Check if the ncm storage handle has already been retrieved. */ - if (serviceIsActive(&(ncm_storage->s))) ncmContentStorageClose(ncm_storage); -} - -static bool titleLoadPersistentStorageTitleInfo(void) -{ - /* Return right away if title info has already been retrieved. */ - if ((g_titleInfo && *g_titleInfo) || g_titleInfoCount) return true; - - for(u8 i = NcmStorageId_BuiltInSystem; i <= NcmStorageId_SdCard; i++) - { - u32 cur_title_count = 0; - - /* Generate title info from the current storage. */ - if (!titleGenerateTitleInfoFromStorage(i)) - { - LOG_MSG("Failed to generate title info from storage ID %u!", i); - return false; - } - - /* Update title info counters and indexes. */ - switch(i) - { - case NcmStorageId_BuiltInSystem: - g_titleInfoBuiltInSystemCount = g_titleInfoCount; - cur_title_count = g_titleInfoBuiltInSystemCount; - break; - case NcmStorageId_BuiltInUser: - g_titleInfoBuiltInUserStartIndex = g_titleInfoBuiltInSystemCount; - g_titleInfoBuiltInUserCount = (g_titleInfoCount - g_titleInfoBuiltInUserStartIndex); - cur_title_count = g_titleInfoBuiltInUserCount; - break; - case NcmStorageId_SdCard: - g_titleInfoSdCardStartIndex = (g_titleInfoBuiltInUserStartIndex + g_titleInfoBuiltInUserCount); - g_titleInfoSdCardCount = (g_titleInfoCount - g_titleInfoSdCardStartIndex); - cur_title_count = g_titleInfoSdCardCount; - break; - default: - break; - } - - LOG_MSG("Loaded info for %u title(s) from storage ID %u.", cur_title_count, i); - } - - return true; -} - -static bool titleGenerateTitleInfoFromStorage(u8 storage_id) -{ - NcmContentMetaDatabase *ncm_db = NULL; - - if (!(ncm_db = titleGetNcmDatabaseByStorageId(storage_id)) || !serviceIsActive(&(ncm_db->s))) + if (!ncm_db || !serviceIsActive(&(ncm_db->s)) || !out_meta_keys || !out_meta_key_count) { LOG_MSG("Invalid parameters!"); return false; } Result rc = 0; - - u32 written = 0, total = 0, extra_title_count = 0; + u32 written = 0, total = 0; NcmContentMetaKey *meta_keys = NULL, *meta_keys_tmp = NULL; size_t meta_keys_size = sizeof(NcmContentMetaKey); - - bool success = false, free_entries = false; + bool success = false; /* Allocate memory for the ncm application content meta keys. */ meta_keys = calloc(1, meta_keys_size); @@ -1528,6 +1733,7 @@ static bool titleGenerateTitleInfoFromStorage(u8 storage_id) /* If it wasn't, odds are there are no titles in this storage. */ if (!written || !total) { + *out_meta_key_count = 0; success = true; goto end; } @@ -1565,102 +1771,21 @@ static bool titleGenerateTitleInfoFromStorage(u8 storage_id) } } - /* Reallocate title info pointer array. */ - if (!titleReallocateTitleInfo(total, false)) - { - LOG_MSG("Failed to reallocate title info pointer array for storage ID %u!", storage_id); - goto end; - } + /* Update output. */ + *out_meta_keys = meta_keys; + *out_meta_key_count = total; - free_entries = true; - - /* Fill new title info entries. */ - for(u32 i = 0; i < total; i++) - { - u64 tmp_size = 0; - NcmContentMetaKey *cur_meta_key = &(meta_keys[i]); - - TitleInfo *cur_title_info = g_titleInfo[g_titleInfoCount + extra_title_count]; - if (!cur_title_info) - { - /* Allocate memory for a new entry. */ - cur_title_info = calloc(1, sizeof(TitleInfo)); - if (!cur_title_info) - { - LOG_MSG("Failed to allocate memory for title info entry #%u! (%u / %u).", extra_title_count, i + 1, total); - goto end; - } - - /* Set title info entry pointer. */ - g_titleInfo[g_titleInfoCount + extra_title_count] = cur_title_info; - } - - /* Get content infos. */ - if (!titleGetContentInfosFromTitle(storage_id, cur_meta_key, &(cur_title_info->content_infos), &(cur_title_info->content_count))) - { - LOG_MSG("Failed to get content infos for title ID %016lX!", cur_meta_key->id); - continue; - } - - /* Calculate title size. */ - for(u32 j = 0; j < cur_title_info->content_count; j++) - { - titleConvertNcmContentSizeToU64(cur_title_info->content_infos[j].size, &tmp_size); - cur_title_info->size += tmp_size; - } - - /* Fill information. */ - cur_title_info->storage_id = storage_id; - memcpy(&(cur_title_info->meta_key), cur_meta_key, sizeof(NcmContentMetaKey)); - cur_title_info->version.value = cur_title_info->meta_key.version; - utilsGenerateFormattedSizeString(cur_title_info->size, cur_title_info->size_str, sizeof(cur_title_info->size_str)); - - /* Retrieve application metadata. */ - u64 app_id = (cur_title_info->meta_key.type <= NcmContentMetaType_Application ? cur_title_info->meta_key.id : \ - (cur_title_info->meta_key.type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(cur_title_info->meta_key.id) : \ - (cur_title_info->meta_key.type == NcmContentMetaType_AddOnContent ? titleGetApplicationIdByAddOnContentId(cur_title_info->meta_key.id) : \ - titleGetApplicationIdByDeltaId(cur_title_info->meta_key.id)))); - - cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id); - - /* Increase extra title info counter. */ - extra_title_count++; - } - - /* Check retrieved title info count. */ - if (!extra_title_count) - { - LOG_MSG("Unable to generate title info entries! (%u element[s]).", total); - goto end; - } - - /* Update title info count. */ - g_titleInfoCount += extra_title_count; - - /* Free extra allocated pointers if we didn't use them. */ - if (extra_title_count < total) titleReallocateTitleInfo(0, false); - - /* Update linked lists for user applications, patches and add-on contents. */ - /* This will also keep track of orphan titles - titles with no available application metadata. */ - titleUpdateTitleInfoLinkedLists(); - - /* Update flag. */ success = true; end: - if (meta_keys) free(meta_keys); - - /* Free previously allocated title info pointers. Ignore return value. */ - if (!success && free_entries) titleReallocateTitleInfo(extra_title_count, true); + if (!success && meta_keys) free(meta_keys); return success; } -static bool titleGetContentInfosFromTitle(u8 storage_id, const NcmContentMetaKey *meta_key, NcmContentInfo **out_content_infos, u32 *out_content_count) +static bool titleGetContentInfosForMetaKey(NcmContentMetaDatabase *ncm_db, 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) + if (!ncm_db || !serviceIsActive(&(ncm_db->s)) || !meta_key || !out_content_infos || !out_content_count) { LOG_MSG("Invalid parameters!"); return false; @@ -1734,56 +1859,81 @@ end: static void titleUpdateTitleInfoLinkedLists(void) { - /* Free orphan title info. */ - titleFreeOrphanTitleInfo(); + /* Free orphan title info entries. */ + titleFreeOrphanTitleInfoEntries(); - /* Loop through all available user titles. */ - for(u32 i = g_titleInfoBuiltInUserStartIndex; i < g_titleInfoCount; i++) + /* Loop through all available title storages. */ + for(u8 i = NcmStorageId_GameCard; i <= NcmStorageId_SdCard; i++) { - /* Get pointer to the current title info and reset its linked list pointers. */ - TitleInfo *child_info = g_titleInfo[i]; - if (!child_info || child_info->meta_key.type < NcmContentMetaType_Application || child_info->meta_key.type > NcmContentMetaType_AddOnContent) continue; + /* Don't process system titles. */ + if (i == NcmStorageId_BuiltInSystem) continue; - child_info->parent = child_info->previous = child_info->next = NULL; + TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(i)]); + TitleInfo **titles = title_storage->titles; + u32 title_count = title_storage->title_count; - if (child_info->meta_key.type == NcmContentMetaType_Patch || child_info->meta_key.type == NcmContentMetaType_AddOnContent) + /* Don't proceed if the current storage holds no titles. */ + if (!titles || !title_count) continue; + + /* Process titles from the current storage. */ + for(u32 j = 0; j < title_count; j++) { - /* We're dealing with a patch or an add-on content. */ - /* Retrieve pointer to the first parent user application entry for patches and add-on contents. */ - /* Since gamecard title info entries are always appended to the end of the buffer, this guarantees we will first retrieve an eMMC / SD card entry (if available). */ - for(u32 j = g_titleInfoBuiltInUserStartIndex; j < g_titleInfoCount; j++) + /* Get pointer to the current title info and reset its linked list pointers. */ + /* Don't proceed if we're dealing with a title that's not an user application, patch or add-on content. */ + TitleInfo *child_info = titles[j]; + if (!child_info || child_info->meta_key.type < NcmContentMetaType_Application || child_info->meta_key.type > NcmContentMetaType_AddOnContent) continue; + + child_info->parent = child_info->previous = child_info->next = NULL; + + if (child_info->meta_key.type == NcmContentMetaType_Patch || child_info->meta_key.type == NcmContentMetaType_AddOnContent) { - TitleInfo *parent_info = g_titleInfo[j]; - if (!parent_info) continue; + /* We're dealing with a patch or an add-on content. */ + /* Retrieve pointer to the first parent user application entry for patches and add-on contents. */ + u64 app_id = (child_info->meta_key.type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(child_info->meta_key.id) : \ + titleGetApplicationIdByAddOnContentId(child_info->meta_key.id)); - if (parent_info->meta_key.type == NcmContentMetaType_Application && \ - ((child_info->meta_key.type == NcmContentMetaType_Patch && titleCheckIfPatchIdBelongsToApplicationId(parent_info->meta_key.id, child_info->meta_key.id)) || \ - (child_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(parent_info->meta_key.id, child_info->meta_key.id)))) + child_info->parent = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id); + if (child_info->parent && !child_info->app_metadata) child_info->app_metadata = child_info->parent->app_metadata; + + /* Add orphan title info entry if we have no application metadata. */ + if (!child_info->app_metadata) { - child_info->parent = parent_info; - if (!child_info->app_metadata) child_info->app_metadata = parent_info->app_metadata; - break; + titleAddOrphanTitleInfoEntry(child_info); + continue; } } - /* If we have no application metadata, add orphan title info entry. */ - if (!child_info->app_metadata) titleAddOrphanTitleInfoEntry(child_info); - } - - /* Locate previous user application, patch or add-on content entry. */ - /* If it's found, we will update both its next pointer and the previous pointer from the current entry. */ - for(u32 j = i; j > g_titleInfoBuiltInUserStartIndex; j--) - { - TitleInfo *previous_info = g_titleInfo[j - 1]; - if (!previous_info) continue; - - if (previous_info->meta_key.type == child_info->meta_key.type && (((child_info->meta_key.type == NcmContentMetaType_Application || child_info->meta_key.type == NcmContentMetaType_Patch) && \ - previous_info->meta_key.id == child_info->meta_key.id) || (child_info->meta_key.type == NcmContentMetaType_AddOnContent && \ - titleCheckIfAddOnContentIdsAreSiblings(previous_info->meta_key.id, child_info->meta_key.id)))) + /* Locate previous user application, patch or add-on content entry. */ + /* If it's found, we will update both its next pointer and the previous pointer from the current entry. */ + for(u8 k = i; k >= NcmStorageId_GameCard; k--) { - previous_info->next = child_info; - child_info->previous = previous_info; - break; + /* Don't process system titles. And don't proceed if we're currently dealing with the first entry from a storage. */ + if (k == NcmStorageId_BuiltInSystem || (k == i && j == 0)) continue; + + TitleStorage *prev_title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(k)]); + TitleInfo **prev_titles = prev_title_storage->titles; + u32 prev_title_count = prev_title_storage->title_count; + u32 start_idx = (k == i ? j : prev_title_count); + + /* Don't proceed if the current storage holds no titles. */ + if (!prev_titles || !prev_title_count) continue; + + for(u32 l = start_idx; l > 0; l--) + { + TitleInfo *prev_info = prev_titles[l - 1]; + if (!prev_info) continue; + + if (prev_info->meta_key.type == child_info->meta_key.type && \ + (((child_info->meta_key.type == NcmContentMetaType_Application || child_info->meta_key.type == NcmContentMetaType_Patch) && prev_info->meta_key.id == child_info->meta_key.id) || \ + (child_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdsAreSiblings(prev_info->meta_key.id, child_info->meta_key.id)))) + { + prev_info->next = child_info; + child_info->previous = prev_info; + break; + } + } + + if (child_info->previous) break; } } } @@ -1815,11 +1965,9 @@ static void titleGameCardInfoThreadFunc(void *arg) Result rc = 0; int idx = 0; - bool first_run = true; Waiter gamecard_status_event_waiter = waiterForUEvent(g_titleGameCardStatusChangeUserEvent); Waiter exit_event_waiter = waiterForUEvent(&g_titleGameCardInfoThreadExitEvent); - Waiter update_info_waiter = waiterForUEvent(&g_titleGameCardUpdateInfoUserEvent); while(true) { @@ -1830,34 +1978,8 @@ static void titleGameCardInfoThreadFunc(void *arg) /* Exit event triggered. */ if (idx == 1) break; - if (!first_run) - { - /* Update gamecard info updated flag. */ - SCOPED_LOCK(&g_titleMutex) g_titleGameCardInfoUpdated = true; - - /* Wait until another function signals us (titleIsGameCardInfoUpdated or titleExit). */ - rc = waitMulti(&idx, -1, update_info_waiter, exit_event_waiter); - if (R_FAILED(rc)) - { - SCOPED_LOCK(&g_titleMutex) g_titleGameCardInfoUpdated = false; - continue; - } - - /* Exit event triggered. */ - if (idx == 1) break; - } - /* Update gamecard title info. */ - SCOPED_LOCK(&g_titleMutex) g_titleGameCardInfoUpdated = (titleRefreshGameCardTitleInfo() && !first_run); - - if (first_run) - { - /* Disable first run flag. */ - first_run = false; - } else { - /* Wake up titleIsGameCardInfoUpdated(). */ - condvarWakeAll(&g_gameCardCondVar); - } + SCOPED_LOCK(&g_titleMutex) g_titleGameCardInfoUpdated = titleRefreshGameCardTitleInfo(); } /* Update gamecard flags. */ @@ -1868,7 +1990,9 @@ static void titleGameCardInfoThreadFunc(void *arg) static bool titleRefreshGameCardTitleInfo(void) { - u32 gamecard_app_count = 0, extra_app_count = 0; + TitleStorage *title_storage = NULL; + TitleInfo **titles = NULL; + u32 title_count = 0, gamecard_app_count = 0, extra_app_count = 0; bool status = false, success = false, cleanup = true, free_entries = false; /* Retrieve current gamecard status. */ @@ -1879,35 +2003,29 @@ static bool titleRefreshGameCardTitleInfo(void) goto end; } - /* Open gamecard ncm database and storage handles. */ - if (!titleOpenNcmDatabaseAndStorageFromGameCard()) + /* Initialize gamecard title storage. */ + if (!titleInitializeTitleStorage(NcmStorageId_GameCard)) { - LOG_MSG("Failed to open gamecard ncm database and storage handles."); + LOG_MSG("Failed to initialize gamecard title storage!"); goto end; } - /* Update start index for the gamecard title info entries. */ - g_titleInfoGameCardStartIndex = g_titleInfoCount; + /* Get gamecard title storage info. */ + title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(NcmStorageId_GameCard)]); + titles = title_storage->titles; + title_count = title_storage->title_count; - /* Generate gamecard title info. */ - if (!titleGenerateTitleInfoFromStorage(NcmStorageId_GameCard)) + /* Verify title count. */ + if (!title_count) { - LOG_MSG("Failed to generate gamecard title info!"); - goto end; - } - - /* Update gamecard title info count. */ - g_titleInfoGameCardCount = (g_titleInfoCount - g_titleInfoGameCardStartIndex); - if (!g_titleInfoGameCardCount) - { - LOG_MSG("Empty content meta key count from gamecard!"); + LOG_MSG("Gamecard title count is zero!"); goto end; } /* Get gamecard user application count. */ - for(u32 i = g_titleInfoGameCardStartIndex; i < g_titleInfoCount; i++) + for(u32 i = 0; i < title_count; i++) { - TitleInfo *cur_title_info = g_titleInfo[i]; + TitleInfo *cur_title_info = titles[i]; if (cur_title_info && cur_title_info->meta_key.type == NcmContentMetaType_Application) gamecard_app_count++; } @@ -1920,7 +2038,7 @@ static bool titleRefreshGameCardTitleInfo(void) } /* Reallocate application metadata pointer array. */ - if (!titleReallocateApplicationMetadata(gamecard_app_count, false)) + if (!titleReallocateApplicationMetadata(gamecard_app_count, false, false)) { LOG_MSG("Failed to reallocate application metadata pointer array for gamecard user applications!"); goto end; @@ -1929,9 +2047,9 @@ static bool titleRefreshGameCardTitleInfo(void) free_entries = true; /* Retrieve application metadata for gamecard user applications. */ - for(u32 i = g_titleInfoGameCardStartIndex; i < g_titleInfoCount; i++) + for(u32 i = 0; i < title_count; i++) { - TitleInfo *cur_title_info = g_titleInfo[i]; + TitleInfo *cur_title_info = titles[i]; if (!cur_title_info) continue; u64 app_id = (cur_title_info->meta_key.type <= NcmContentMetaType_Application ? cur_title_info->meta_key.id : \ @@ -1940,10 +2058,10 @@ static bool titleRefreshGameCardTitleInfo(void) titleGetApplicationIdByDeltaId(cur_title_info->meta_key.id)))); /* Do not proceed if application metadata has already been retrieved, or if we can successfully retrieve it. */ - if (cur_title_info->app_metadata != NULL || (cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id)) != NULL) continue; + if (cur_title_info->app_metadata != NULL || (cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, false)) != NULL) continue; /* Retrieve application metadata pointer. */ - TitleApplicationMetadata *cur_app_metadata = g_appMetadata[g_appMetadataCount + extra_app_count]; + TitleApplicationMetadata *cur_app_metadata = g_userMetadata[g_userMetadataCount + extra_app_count]; if (!cur_app_metadata) { /* Allocate memory for a new application metadata entry. */ @@ -1955,11 +2073,11 @@ static bool titleRefreshGameCardTitleInfo(void) } /* Set application metadata entry pointer. */ - g_appMetadata[g_appMetadataCount + extra_app_count] = cur_app_metadata; + g_userMetadata[g_userMetadataCount + extra_app_count] = cur_app_metadata; } /* Retrieve application metadata. */ - if (!titleRetrieveApplicationMetadataByTitleId(app_id, cur_app_metadata)) continue; + if (!titleRetrieveUserApplicationMetadataByTitleId(app_id, cur_app_metadata)) continue; /* Update application metadata pointer in title info. */ cur_title_info->app_metadata = cur_app_metadata; @@ -1971,22 +2089,24 @@ static bool titleRefreshGameCardTitleInfo(void) if (extra_app_count) { /* Update application metadata count. */ - g_appMetadataCount += extra_app_count; + g_userMetadataCount += extra_app_count; /* Sort application metadata entries by name. */ - u32 user_app_metadata_count = (g_appMetadataCount - g_systemTitlesCount); - if (user_app_metadata_count > 1) qsort(g_appMetadata + g_systemTitlesCount, user_app_metadata_count, sizeof(TitleApplicationMetadata*), &titleUserApplicationMetadataEntrySortFunction); + if (g_userMetadataCount > 1) qsort(g_userMetadata, g_userMetadataCount, sizeof(TitleApplicationMetadata*), &titleUserApplicationMetadataEntrySortFunction); /* Update linked lists for user applications, patches and add-on contents. */ /* This will take care of orphan titles we might now have application metadata for. */ titleUpdateTitleInfoLinkedLists(); - } else { + } else + if (g_userMetadata[g_userMetadataCount]) + { /* Free leftover application metadata entry (if needed). */ - if (g_appMetadata[g_appMetadataCount]) free(g_appMetadata[g_appMetadataCount]); + free(g_userMetadata[g_userMetadataCount]); + g_userMetadata[g_userMetadataCount] = NULL; } /* Free extra allocated pointers if we didn't use them. */ - if (extra_app_count < gamecard_app_count) titleReallocateApplicationMetadata(0, false); + if (extra_app_count < gamecard_app_count) titleReallocateApplicationMetadata(0, false, false); /* Update flags. */ success = true; @@ -1997,48 +2117,40 @@ end: g_titleGameCardAvailable = status; /* Free previously allocated application metadata pointers. Ignore return value. */ - if (!success && free_entries) titleReallocateApplicationMetadata(extra_app_count, true); + if (!success && free_entries) titleReallocateApplicationMetadata(extra_app_count, false, true); - /* Remove gamecard title info entries and close its ncm database and storage handles (if needed). */ if (cleanup) { - titleRemoveGameCardTitleInfoEntries(); - titleCloseNcmDatabaseAndStorageFromGameCard(); + /* Close gamecard title storage. */ + titleCloseTitleStorage(NcmStorageId_GameCard); + + /* Update linked lists for user applications, patches and add-on contents. */ + titleUpdateTitleInfoLinkedLists(); } return success; } -static void titleRemoveGameCardTitleInfoEntries(void) -{ - if (!g_titleInfo || !*g_titleInfo || !g_titleInfoCount || !g_titleInfoGameCardCount) return; - - /* Update title info count. */ - g_titleInfoCount = g_titleInfoGameCardStartIndex; - - /* Reallocate title info pointer array. */ - titleReallocateTitleInfo(g_titleInfoGameCardCount - 1, true); - - /* Update gamecard variables. */ - g_titleInfoGameCardStartIndex = g_titleInfoGameCardCount = 0; - - /* Update linked lists for user applications, patches and add-on contents. */ - /* Won't do anything if g_titleInfoCount is already zero. */ - titleUpdateTitleInfoLinkedLists(); -} - static bool titleIsUserApplicationContentAvailable(u64 app_id) { - if (!g_titleInfo || !*g_titleInfo || !g_titleInfoCount || !app_id) return false; + if (!app_id) return false; - for(u32 i = g_titleInfoBuiltInUserStartIndex; i < g_titleInfoCount; i++) + for(u8 i = NcmStorageId_GameCard; i <= NcmStorageId_SdCard; i++) { - TitleInfo *cur_title_info = g_titleInfo[i]; - if (!cur_title_info) continue; + if (i == NcmStorageId_BuiltInSystem) continue; - if ((cur_title_info->meta_key.type == NcmContentMetaType_Application && cur_title_info->meta_key.id == app_id) || \ - (cur_title_info->meta_key.type == NcmContentMetaType_Patch && titleCheckIfPatchIdBelongsToApplicationId(app_id, cur_title_info->meta_key.id)) || \ - (cur_title_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, cur_title_info->meta_key.id))) return true; + TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(i)]); + if (!title_storage->titles || !*(title_storage->titles) || !title_storage->title_count) continue; + + for(u32 j = 0; j < title_storage->title_count; j++) + { + TitleInfo *title_info = title_storage->titles[j]; + if (!title_info) continue; + + if ((title_info->meta_key.type == NcmContentMetaType_Application && title_info->meta_key.id == app_id) || \ + (title_info->meta_key.type == NcmContentMetaType_Patch && titleCheckIfPatchIdBelongsToApplicationId(app_id, title_info->meta_key.id)) || \ + (title_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, title_info->meta_key.id))) return true; + } } return false; @@ -2046,40 +2158,176 @@ static bool titleIsUserApplicationContentAvailable(u64 app_id) static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id) { - TitleInfo *info = NULL; - - u32 start_idx = ((storage_id == NcmStorageId_BuiltInSystem || storage_id == NcmStorageId_Any) ? 0 : \ - (storage_id == NcmStorageId_BuiltInUser ? g_titleInfoBuiltInUserStartIndex : \ - (storage_id == NcmStorageId_SdCard ? g_titleInfoSdCardStartIndex : g_titleInfoGameCardStartIndex))); - - u32 max_val = (storage_id == NcmStorageId_BuiltInSystem ? g_titleInfoBuiltInSystemCount : \ - (storage_id == NcmStorageId_BuiltInUser ? g_titleInfoBuiltInUserCount : \ - (storage_id == NcmStorageId_SdCard ? g_titleInfoSdCardCount : \ - (storage_id == NcmStorageId_GameCard ? g_titleInfoGameCardCount : g_titleInfoCount)))); - - max_val += start_idx; - - if (!g_titleInterfaceInit || !g_titleInfo || !*g_titleInfo || !g_titleInfoCount || storage_id < NcmStorageId_GameCard || storage_id > NcmStorageId_Any || max_val == start_idx || \ - max_val > g_titleInfoCount || start_idx > g_titleInfoCount || !title_id) + if (storage_id < NcmStorageId_GameCard || storage_id > NcmStorageId_Any || !title_id) { LOG_MSG("Invalid parameters!"); - goto end; + return NULL; } - for(u32 i = start_idx; i < max_val; i++) + TitleInfo *out = NULL; + + u8 start_idx = (storage_id == NcmStorageId_Any ? TITLE_STORAGE_INDEX(NcmStorageId_GameCard) : TITLE_STORAGE_INDEX(storage_id)); + u8 max_val = (storage_id == NcmStorageId_Any ? TITLE_STORAGE_INDEX(NcmStorageId_SdCard) : start_idx); + + for(u8 i = start_idx; i <= max_val; i++) { - TitleInfo *title_info = g_titleInfo[i]; - if (title_info && title_info->meta_key.id == title_id) + TitleStorage *title_storage = &(g_titleStorage[i]); + if (!title_storage->titles || !*(title_storage->titles) || !title_storage->title_count) continue; + + for(u32 j = 0; j < title_storage->title_count; j++) { - info = title_info; - break; + TitleInfo *title_info = title_storage->titles[j]; + if (title_info && title_info->meta_key.id == title_id) + { + out = title_info; + break; + } } + + if (out) break; } //if (!info) LOG_MSG("Unable to find title info entry with ID \"%016lX\"! (storage ID %u).", title_id, storage_id); + return out; +} + +static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *parent, TitleInfo *previous, TitleInfo *next) +{ + if (!title_info || title_info->storage_id < NcmStorageId_GameCard || title_info->storage_id > NcmStorageId_SdCard || !title_info->meta_key.id || \ + (title_info->meta_key.type > NcmContentMetaType_BootImagePackageSafe && title_info->meta_key.type < NcmContentMetaType_Application) || title_info->meta_key.type > NcmContentMetaType_Delta || \ + !title_info->content_count || !title_info->content_infos) + { + LOG_MSG("Invalid parameters!"); + return NULL; + } + + TitleInfo *title_info_dup = NULL, *tmp1 = NULL, *tmp2 = NULL; + NcmContentInfo *content_infos_dup = NULL; + bool dup_parent = false, dup_previous = false, dup_next = false, success = false; + + /* Allocate memory for the new TitleInfo element. */ + title_info_dup = calloc(1, sizeof(TitleInfo)); + if (!title_info_dup) + { + LOG_MSG("Failed to allocate memory for TitleInfo duplicate!"); + return NULL; + } + + /* Copy TitleInfo data. */ + memcpy(title_info_dup, title_info, sizeof(TitleInfo)); + title_info_dup->parent = title_info_dup->previous = title_info_dup->next = NULL; + + /* Allocate memory for NcmContentInfo elements. */ + content_infos_dup = calloc(title_info->content_count, sizeof(NcmContentInfo)); + if (!content_infos_dup) + { + LOG_MSG("Failed to allocate memory for NcmContentInfo duplicates!"); + goto end; + } + + /* Copy NcmContentInfo data. */ + memcpy(content_infos_dup, title_info->content_infos, title_info->content_count * sizeof(NcmContentInfo)); + + /* Update content infos pointer. */ + title_info_dup->content_infos = content_infos_dup; + + /* Duplicate linked list data. */ + if (title_info->parent) + { + if (parent) + { + title_info_dup->parent = parent; + } else { + title_info_dup->parent = titleDuplicateTitleInfo(title_info->parent, NULL, NULL, NULL); + if (!title_info_dup->parent) goto end; + dup_parent = true; + } + + /* Update pointer to parent title info - this will be used while duplicating siblings. */ + parent = title_info_dup->parent; + } + + if (title_info->previous) + { + if (previous) + { + title_info_dup->previous = previous; + } else { + title_info_dup->previous = titleDuplicateTitleInfo(title_info->previous, parent, NULL, title_info_dup); + if (!title_info_dup->previous) goto end; + dup_previous = true; + } + } + + if (title_info->next) + { + if (next) + { + title_info_dup->next = next; + } else { + title_info_dup->next = titleDuplicateTitleInfo(title_info->next, parent, title_info_dup, NULL); + if (!title_info_dup->next) goto end; + dup_next = true; + } + } + + /* Update flag. */ + success = true; + end: - return info; + /* We can't directly use titleFreeTitleInfo() on title_info_dup because some or all of the linked list data may have been provided as function arguments. */ + /* So we'll take care of freeing data the old fashioned way. */ + if (!success) + { + if (content_infos_dup) + { + free(content_infos_dup); + content_infos_dup = NULL; + } + + if (title_info_dup) + { + /* Free parent linked list (if duplicated). */ + /* Parent title infos are user applications with no reference to child titles, so it should be safe to free them first with titleFreeTitleInfo(). */ + if (dup_parent) titleFreeTitleInfo(&(title_info_dup->parent)); + + /* Free previous sibling(s) (if duplicated). */ + /* We need to take care not to free the parent linked list, either because we may have already freed it, or because it may have been passed as an argument. */ + /* Furthermore, the "next" pointer from the previous sibling points to our current duplicated entry, so we need to clear it. */ + if (dup_previous) + { + tmp1 = title_info_dup->previous; + while(tmp1) + { + tmp2 = tmp1->previous; + tmp1->parent = tmp1->previous = tmp1->next = NULL; + titleFreeTitleInfo(&tmp1); + tmp1 = tmp2; + } + } + + /* Free next sibling(s) (if duplicated). */ + /* We need to take care not to free the parent linked list, either because we may have already freed it, or because it may have been passed as an argument. */ + /* Furthermore, the "previous" pointer from the next sibling points to our current duplicated entry, so we need to clear it. */ + if (dup_next) + { + tmp1 = title_info_dup->next; + while(tmp1) + { + tmp2 = tmp1->next; + tmp1->parent = tmp1->previous = tmp1->next = NULL; + titleFreeTitleInfo(&tmp1); + tmp1 = tmp2; + } + } + + free(title_info_dup); + title_info_dup = NULL; + } + } + + return title_info_dup; } static int titleSystemTitleMetadataEntrySortFunction(const void *a, const void *b) diff --git a/todo.txt b/todo.txt index 846267d..d22994f 100644 --- a/todo.txt +++ b/todo.txt @@ -8,14 +8,12 @@ todo: usb: improve usbIsReady - title: orphan system titles - title: come up with a better way to handle gamecard titles title: more functions for title lookup? (filters, patches / aoc, etc.) title: more functions for content lookup? (based on id) title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles + title: manually retrieve version string from control data while generating filenames? gamecard: functions to display filelist - gamecard: move around code to perform the lafw version check at the places we need pfs0: functions to display filelist