diff --git a/code_templates/dump_title_infos.c b/code_templates/dump_title_infos.c index c645c57..9234da3 100644 --- a/code_templates/dump_title_infos.c +++ b/code_templates/dump_title_infos.c @@ -26,7 +26,7 @@ utilsGenerateHexStringFromData(content_id_str, sizeof(content_id_str), g_titleInfo[i].content_infos[j].content_id.c, sizeof(g_titleInfo[i].content_infos[j].content_id.c), false); u64 content_size = 0; - titleConvertNcmContentSizeToU64(g_titleInfo[i].content_infos[j].size, &content_size); + ncmContentInfoSizeToU64(&(g_titleInfo[i].content_infos[j]), &content_size); char content_size_str[32] = {0}; utilsGenerateFormattedSizeString(content_size, content_size_str, sizeof(content_size_str)); @@ -77,4 +77,4 @@ title_infos_txt = NULL; utilsCommitSdCardFileSystemChanges(); } - } \ No newline at end of file + } diff --git a/include/core/cnmt.h b/include/core/cnmt.h index 0b25774..3eca57d 100644 --- a/include/core/cnmt.h +++ b/include/core/cnmt.h @@ -96,15 +96,27 @@ typedef struct { NXDT_ASSERT(ContentMetaPatchMetaExtendedHeader, 0x18); -/// Extended header for AddOnContent titles. +/// Extended header for AddOnContent tiles (15.0.0+). /// Equivalent to NcmAddOnContentMetaExtendedHeader, but using a Version struct. typedef struct { u64 application_id; Version required_application_version; - u8 reserved[0x4]; + u8 content_accessibilities; /// TODO: find out purpose / how to use? + u8 reserved[0x3]; + u64 data_patch_id; } ContentMetaAddOnContentMetaExtendedHeader; -NXDT_ASSERT(ContentMetaAddOnContentMetaExtendedHeader, 0x10); +NXDT_ASSERT(ContentMetaAddOnContentMetaExtendedHeader, 0x18); + +/// Old extended header for AddOnContent titles (1.0.0 - 14.1.2). +/// Equivalent to NcmLegacyAddOnContentMetaExtendedHeader, but using a Version struct. +typedef struct { + u64 application_id; + Version required_application_version; + u8 reserved[0x4]; +} ContentMetaLegacyAddOnContentMetaExtendedHeader; + +NXDT_ASSERT(ContentMetaLegacyAddOnContentMetaExtendedHeader, 0x10); /// Extended header for Delta titles. typedef struct { @@ -115,6 +127,18 @@ typedef struct { NXDT_ASSERT(ContentMetaDeltaMetaExtendedHeader, 0x10); +/// Extended header for DataPatch titles. +/// Equivalent to NcmDataPatchMetaExtendedHeader, but using a Version struct. +typedef struct { + u64 data_id; + u64 application_id; + Version required_application_version; + u32 extended_data_size; + u8 reserved[0x8]; +} ContentMetaDataPatchMetaExtendedHeader; + +NXDT_ASSERT(ContentMetaDataPatchMetaExtendedHeader, 0x20); + typedef enum { ContentMetaFirmwareVariationVersion_Invalid = 0, ContentMetaFirmwareVariationVersion_V1 = 1, @@ -314,16 +338,38 @@ NX_INLINE bool cnmtIsValidContext(ContentMetaContext *cnmt_ctx) NX_INLINE u64 cnmtGetRequiredTitleId(ContentMetaContext *cnmt_ctx) { - return ((cnmtIsValidContext(cnmt_ctx) && (cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Application || \ - cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Patch || cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_AddOnContent)) ? \ - *((u64*)cnmt_ctx->extended_header) : 0); + if (!cnmtIsValidContext(cnmt_ctx)) return 0; + + u8 content_meta_type = cnmt_ctx->packaged_header->content_meta_type; + + if (content_meta_type == NcmContentMetaType_Application || content_meta_type == NcmContentMetaType_Patch || content_meta_type == NcmContentMetaType_AddOnContent) + { + return *((u64*)cnmt_ctx->extended_header); + } else + if (content_meta_type == NcmContentMetaType_DataPatch) + { + return ((ContentMetaDataPatchMetaExtendedHeader*)cnmt_ctx->extended_header)->application_id; + } + + return 0; } NX_INLINE u32 cnmtGetRequiredTitleVersion(ContentMetaContext *cnmt_ctx) { - return ((cnmtIsValidContext(cnmt_ctx) && (cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Application || \ - cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Patch || cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_AddOnContent)) ? \ - ((Version*)(cnmt_ctx->extended_header + sizeof(u64)))->value : 0); + if (!cnmtIsValidContext(cnmt_ctx)) return 0; + + u8 content_meta_type = cnmt_ctx->packaged_header->content_meta_type; + + if (content_meta_type == NcmContentMetaType_Application || content_meta_type == NcmContentMetaType_Patch || content_meta_type == NcmContentMetaType_AddOnContent) + { + return ((Version*)(cnmt_ctx->extended_header + sizeof(u64)))->value; + } else + if (content_meta_type == NcmContentMetaType_DataPatch) + { + return ((ContentMetaDataPatchMetaExtendedHeader*)cnmt_ctx->extended_header)->required_application_version.value; + } + + return 0; } #ifdef __cplusplus diff --git a/include/core/nca.h b/include/core/nca.h index 320312c..983e60c 100644 --- a/include/core/nca.h +++ b/include/core/nca.h @@ -91,7 +91,8 @@ typedef enum { NcaKeyGeneration_Since1210NUP = 12, ///< 12.1.0. NcaKeyGeneration_Since1300NUP = 13, ///< 13.0.0 - 13.2.1. NcaKeyGeneration_Since1400NUP = 14, ///< 14.0.0 - 14.1.2. - NcaKeyGeneration_Current = NcaKeyGeneration_Since1400NUP, + NcaKeyGeneration_Since1500NUP = 15, ///< 15.0.0. + NcaKeyGeneration_Current = NcaKeyGeneration_Since1500NUP, NcaKeyGeneration_Max = 32 } NcaKeyGeneration; @@ -103,6 +104,7 @@ typedef enum { } NcaKeyAreaEncryptionKeyIndex; /// 'NcaSignatureKeyGeneration_Current' will always point to the last known key generation value. +/// TODO: update on signature keygen changes. typedef enum { NcaSignatureKeyGeneration_Since100NUP = 0, ///< 1.0.0 - 8.1.1. NcaSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0 - 14.1.2. diff --git a/include/core/npdm.h b/include/core/npdm.h index ec8a18d..5f89ac1 100644 --- a/include/core/npdm.h +++ b/include/core/npdm.h @@ -40,6 +40,7 @@ extern "C" { #define NPDM_MAIN_THREAD_STACK_SIZE_ALIGNMENT 0x1000 /// 'NpdmSignatureKeyGeneration_Current' will always point to the last known key generation value. +/// TODO: update on signature keygen changes. typedef enum { NpdmSignatureKeyGeneration_Since100NUP = 0, ///< 1.0.0 - 8.1.1. NpdmSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0 - 14.1.2. @@ -152,45 +153,47 @@ typedef struct { NXDT_ASSERT(NpdmAciHeader, 0x40); typedef enum { - NpdmFsAccessControlFlags_ApplicationInfo = BIT_LONG(0), - NpdmFsAccessControlFlags_BootModeControl = BIT_LONG(1), - NpdmFsAccessControlFlags_Calibration = BIT_LONG(2), - NpdmFsAccessControlFlags_SystemSaveData = BIT_LONG(3), - NpdmFsAccessControlFlags_GameCard = BIT_LONG(4), - NpdmFsAccessControlFlags_SaveDataBackUp = BIT_LONG(5), - NpdmFsAccessControlFlags_SaveDataManagement = BIT_LONG(6), - NpdmFsAccessControlFlags_BisAllRaw = BIT_LONG(7), - NpdmFsAccessControlFlags_GameCardRaw = BIT_LONG(8), - NpdmFsAccessControlFlags_GameCardPrivate = BIT_LONG(9), - NpdmFsAccessControlFlags_SetTime = BIT_LONG(10), - NpdmFsAccessControlFlags_ContentManager = BIT_LONG(11), - NpdmFsAccessControlFlags_ImageManager = BIT_LONG(12), - NpdmFsAccessControlFlags_CreateSaveData = BIT_LONG(13), - NpdmFsAccessControlFlags_SystemSaveDataManagement = BIT_LONG(14), - NpdmFsAccessControlFlags_BisFileSystem = BIT_LONG(15), - NpdmFsAccessControlFlags_SystemUpdate = BIT_LONG(16), - NpdmFsAccessControlFlags_SaveDataMeta = BIT_LONG(17), - NpdmFsAccessControlFlags_DeviceSaveData = BIT_LONG(18), - NpdmFsAccessControlFlags_SettingsControl = BIT_LONG(19), - NpdmFsAccessControlFlags_SystemData = BIT_LONG(20), - NpdmFsAccessControlFlags_SdCard = BIT_LONG(21), - NpdmFsAccessControlFlags_Host = BIT_LONG(22), - NpdmFsAccessControlFlags_FillBis = BIT_LONG(23), - NpdmFsAccessControlFlags_CorruptSaveData = BIT_LONG(24), - NpdmFsAccessControlFlags_SaveDataForDebug = BIT_LONG(25), - NpdmFsAccessControlFlags_FormatSdCard = BIT_LONG(26), - NpdmFsAccessControlFlags_GetRightsId = BIT_LONG(27), - NpdmFsAccessControlFlags_RegisterExternalKey = BIT_LONG(28), - NpdmFsAccessControlFlags_RegisterUpdatePartition = BIT_LONG(29), - NpdmFsAccessControlFlags_SaveDataTransfer = BIT_LONG(30), - NpdmFsAccessControlFlags_DeviceDetection = BIT_LONG(31), - NpdmFsAccessControlFlags_AccessFailureResolution = BIT_LONG(32), - NpdmFsAccessControlFlags_SaveDataTransferVersion2 = BIT_LONG(33), - NpdmFsAccessControlFlags_RegisterProgramIndexMapInfo = BIT_LONG(34), - NpdmFsAccessControlFlags_CreateOwnSaveData = BIT_LONG(35), - NpdmFsAccessControlFlags_MoveCacheStorage = BIT_LONG(36), - NpdmFsAccessControlFlags_Debug = BIT_LONG(62), - NpdmFsAccessControlFlags_FullPermission = BIT_LONG(63) + NpdmFsAccessControlFlags_ApplicationInfo = BIT_LONG(0), + NpdmFsAccessControlFlags_BootModeControl = BIT_LONG(1), + NpdmFsAccessControlFlags_Calibration = BIT_LONG(2), + NpdmFsAccessControlFlags_SystemSaveData = BIT_LONG(3), + NpdmFsAccessControlFlags_GameCard = BIT_LONG(4), + NpdmFsAccessControlFlags_SaveDataBackUp = BIT_LONG(5), + NpdmFsAccessControlFlags_SaveDataManagement = BIT_LONG(6), + NpdmFsAccessControlFlags_BisAllRaw = BIT_LONG(7), + NpdmFsAccessControlFlags_GameCardRaw = BIT_LONG(8), + NpdmFsAccessControlFlags_GameCardPrivate = BIT_LONG(9), + NpdmFsAccessControlFlags_SetTime = BIT_LONG(10), + NpdmFsAccessControlFlags_ContentManager = BIT_LONG(11), + NpdmFsAccessControlFlags_ImageManager = BIT_LONG(12), + NpdmFsAccessControlFlags_CreateSaveData = BIT_LONG(13), + NpdmFsAccessControlFlags_SystemSaveDataManagement = BIT_LONG(14), + NpdmFsAccessControlFlags_BisFileSystem = BIT_LONG(15), + NpdmFsAccessControlFlags_SystemUpdate = BIT_LONG(16), + NpdmFsAccessControlFlags_SaveDataMeta = BIT_LONG(17), + NpdmFsAccessControlFlags_DeviceSaveData = BIT_LONG(18), + NpdmFsAccessControlFlags_SettingsControl = BIT_LONG(19), + NpdmFsAccessControlFlags_SystemData = BIT_LONG(20), + NpdmFsAccessControlFlags_SdCard = BIT_LONG(21), + NpdmFsAccessControlFlags_Host = BIT_LONG(22), + NpdmFsAccessControlFlags_FillBis = BIT_LONG(23), + NpdmFsAccessControlFlags_CorruptSaveData = BIT_LONG(24), + NpdmFsAccessControlFlags_SaveDataForDebug = BIT_LONG(25), + NpdmFsAccessControlFlags_FormatSdCard = BIT_LONG(26), + NpdmFsAccessControlFlags_GetRightsId = BIT_LONG(27), + NpdmFsAccessControlFlags_RegisterExternalKey = BIT_LONG(28), + NpdmFsAccessControlFlags_RegisterUpdatePartition = BIT_LONG(29), + NpdmFsAccessControlFlags_SaveDataTransfer = BIT_LONG(30), + NpdmFsAccessControlFlags_DeviceDetection = BIT_LONG(31), + NpdmFsAccessControlFlags_AccessFailureResolution = BIT_LONG(32), + NpdmFsAccessControlFlags_SaveDataTransferVersion2 = BIT_LONG(33), + NpdmFsAccessControlFlags_RegisterProgramIndexMapInfo = BIT_LONG(34), + NpdmFsAccessControlFlags_CreateOwnSaveData = BIT_LONG(35), + NpdmFsAccessControlFlags_MoveCacheStorage = BIT_LONG(36), + NpdmFsAccessControlFlags_DeviceTreeBlob = BIT_LONG(37), + NpdmFsAccessControlFlags_NotifyErrorContextServiceReady = BIT_LONG(38), + NpdmFsAccessControlFlags_Debug = BIT_LONG(62), + NpdmFsAccessControlFlags_FullPermission = BIT_LONG(63) } NpdmFsAccessControlFlags; /// FsAccessControl descriptor. Part of the ACID section body. @@ -236,18 +239,19 @@ NXDT_ASSERT(NpdmFsAccessControlData, 0x1C); #pragma pack(push, 1) typedef struct { u32 content_owner_id_count; - u64 content_owner_id[]; ///< 'content_owner_id_count' content owned IDs. + u64 content_owner_id[]; ///< 'content_owner_id_count' content owner IDs. } NpdmFsAccessControlDataContentOwnerBlock; #pragma pack(pop) NXDT_ASSERT(NpdmFsAccessControlDataContentOwnerBlock, 0x4); typedef enum { - NpdmAccessibility_Read = BIT(0), - NpdmAccessibility_Write = BIT(1) + NpdmAccessibility_Read = BIT(0), + NpdmAccessibility_Write = BIT(1), + NpdmAccessibility_ReadWrite = NpdmAccessibility_Read | NpdmAccessibility_Write } NpdmAccessibility; -/// Placed after NpdmFsAccessControlData / NpdmFsAccessControlDataContentOwnerBlock if the 'content_owner_info_size' member from NpdmFsAccessControlData is greater than zero. +/// Placed after NpdmFsAccessControlData / NpdmFsAccessControlDataContentOwnerBlock if the 'save_data_owner_info_size' member from NpdmFsAccessControlData is greater than zero. /// If available, this block is padded to a 0x4-byte boundary and followed by 'save_data_owner_id_count' save data owner IDs. typedef struct { u32 save_data_owner_id_count; @@ -309,144 +313,212 @@ NXDT_ASSERT(NpdmThreadInfo, 0x4); /// System call table. typedef enum { ///< System calls for index 0. - NpdmSystemCallId_Reserved1 = BIT(0), - NpdmSystemCallId_SetHeapSize = BIT(1), - NpdmSystemCallId_SetMemoryPermission = BIT(2), - NpdmSystemCallId_SetMemoryAttribute = BIT(3), - NpdmSystemCallId_MapMemory = BIT(4), - NpdmSystemCallId_UnmapMemory = BIT(5), - NpdmSystemCallId_QueryMemory = BIT(6), - NpdmSystemCallId_ExitProcess = BIT(7), - NpdmSystemCallId_CreateThread = BIT(8), - NpdmSystemCallId_StartThread = BIT(9), - NpdmSystemCallId_ExitThread = BIT(10), - NpdmSystemCallId_SleepThread = BIT(11), - NpdmSystemCallId_GetThreadPriority = BIT(12), - NpdmSystemCallId_SetThreadPriority = BIT(13), - NpdmSystemCallId_GetThreadCoreMask = BIT(14), - NpdmSystemCallId_SetThreadCoreMask = BIT(15), - NpdmSystemCallId_GetCurrentProcessorNumber = BIT(16), - NpdmSystemCallId_SignalEvent = BIT(17), - NpdmSystemCallId_ClearEvent = BIT(18), - NpdmSystemCallId_MapSharedMemory = BIT(19), - NpdmSystemCallId_UnmapSharedMemory = BIT(20), - NpdmSystemCallId_CreateTransferMemory = BIT(21), - NpdmSystemCallId_CloseHandle = BIT(22), - NpdmSystemCallId_ResetSignal = BIT(23), + NpdmSystemCallId_Reserved1 = BIT(0), ///< SVC 0x00. + NpdmSystemCallId_SetHeapSize = BIT(1), ///< SVC 0x01. + NpdmSystemCallId_SetMemoryPermission = BIT(2), ///< SVC 0x02. + NpdmSystemCallId_SetMemoryAttribute = BIT(3), ///< SVC 0x03. + NpdmSystemCallId_MapMemory = BIT(4), ///< SVC 0x04. + NpdmSystemCallId_UnmapMemory = BIT(5), ///< SVC 0x05. + NpdmSystemCallId_QueryMemory = BIT(6), ///< SVC 0x06. + NpdmSystemCallId_ExitProcess = BIT(7), ///< SVC 0x07. + NpdmSystemCallId_CreateThread = BIT(8), ///< SVC 0x08. + NpdmSystemCallId_StartThread = BIT(9), ///< SVC 0x09. + NpdmSystemCallId_ExitThread = BIT(10), ///< SVC 0x0A. + NpdmSystemCallId_SleepThread = BIT(11), ///< SVC 0x0B. + NpdmSystemCallId_GetThreadPriority = BIT(12), ///< SVC 0x0C. + NpdmSystemCallId_SetThreadPriority = BIT(13), ///< SVC 0x0D. + NpdmSystemCallId_GetThreadCoreMask = BIT(14), ///< SVC 0x0E. + NpdmSystemCallId_SetThreadCoreMask = BIT(15), ///< SVC 0x0F. + NpdmSystemCallId_GetCurrentProcessorNumber = BIT(16), ///< SVC 0x10. + NpdmSystemCallId_SignalEvent = BIT(17), ///< SVC 0x11. + NpdmSystemCallId_ClearEvent = BIT(18), ///< SVC 0x12. + NpdmSystemCallId_MapSharedMemory = BIT(19), ///< SVC 0x13. + NpdmSystemCallId_UnmapSharedMemory = BIT(20), ///< SVC 0x14. + NpdmSystemCallId_CreateTransferMemory = BIT(21), ///< SVC 0x15. + NpdmSystemCallId_CloseHandle = BIT(22), ///< SVC 0x16. + NpdmSystemCallId_ResetSignal = BIT(23), ///< SVC 0x17. ///< System calls for index 1. - NpdmSystemCallId_WaitSynchronization = BIT(0), - NpdmSystemCallId_CancelSynchronization = BIT(1), - NpdmSystemCallId_ArbitrateLock = BIT(2), - NpdmSystemCallId_ArbitrateUnlock = BIT(3), - NpdmSystemCallId_WaitProcessWideKeyAtomic = BIT(4), - NpdmSystemCallId_SignalProcessWideKey = BIT(5), - NpdmSystemCallId_GetSystemTick = BIT(6), - NpdmSystemCallId_ConnectToNamedPort = BIT(7), - NpdmSystemCallId_SendSyncRequestLight = BIT(8), - NpdmSystemCallId_SendSyncRequest = BIT(9), - NpdmSystemCallId_SendSyncRequestWithUserBuffer = BIT(10), - NpdmSystemCallId_SendAsyncRequestWithUserBuffer = BIT(11), - NpdmSystemCallId_GetProcessId = BIT(12), - NpdmSystemCallId_GetThreadId = BIT(13), - NpdmSystemCallId_Break = BIT(14), - NpdmSystemCallId_OutputDebugString = BIT(15), - NpdmSystemCallId_ReturnFromException = BIT(16), - NpdmSystemCallId_GetInfo = BIT(17), - NpdmSystemCallId_FlushEntireDataCache = BIT(18), - NpdmSystemCallId_FlushDataCache = BIT(19), - NpdmSystemCallId_MapPhysicalMemory = BIT(20), - NpdmSystemCallId_UnmapPhysicalMemory = BIT(21), - NpdmSystemCallId_GetDebugFutureThreadInfo = BIT(22), ///< Old: SystemCallId_GetFutureThreadInfo. - NpdmSystemCallId_GetLastThreadInfo = BIT(23), + NpdmSystemCallId_WaitSynchronization = BIT(0), ///< SVC 0x18. + NpdmSystemCallId_CancelSynchronization = BIT(1), ///< SVC 0x19. + NpdmSystemCallId_ArbitrateLock = BIT(2), ///< SVC 0x1A. + NpdmSystemCallId_ArbitrateUnlock = BIT(3), ///< SVC 0x1B. + NpdmSystemCallId_WaitProcessWideKeyAtomic = BIT(4), ///< SVC 0x1C. + NpdmSystemCallId_SignalProcessWideKey = BIT(5), ///< SVC 0x1D. + NpdmSystemCallId_GetSystemTick = BIT(6), ///< SVC 0x1E. + NpdmSystemCallId_ConnectToNamedPort = BIT(7), ///< SVC 0x1F. + NpdmSystemCallId_SendSyncRequestLight = BIT(8), ///< SVC 0x20. + NpdmSystemCallId_SendSyncRequest = BIT(9), ///< SVC 0x21. + NpdmSystemCallId_SendSyncRequestWithUserBuffer = BIT(10), ///< SVC 0x22. + NpdmSystemCallId_SendAsyncRequestWithUserBuffer = BIT(11), ///< SVC 0x23. + NpdmSystemCallId_GetProcessId = BIT(12), ///< SVC 0x24. + NpdmSystemCallId_GetThreadId = BIT(13), ///< SVC 0x25. + NpdmSystemCallId_Break = BIT(14), ///< SVC 0x26. + NpdmSystemCallId_OutputDebugString = BIT(15), ///< SVC 0x27. + NpdmSystemCallId_ReturnFromException = BIT(16), ///< SVC 0x28. + NpdmSystemCallId_GetInfo = BIT(17), ///< SVC 0x29. + NpdmSystemCallId_FlushEntireDataCache = BIT(18), ///< SVC 0x2A. + NpdmSystemCallId_FlushDataCache = BIT(19), ///< SVC 0x2B. + NpdmSystemCallId_MapPhysicalMemory = BIT(20), ///< SVC 0x2C (3.0.0+). + NpdmSystemCallId_UnmapPhysicalMemory = BIT(21), ///< SVC 0x2D (3.0.0+). + NpdmSystemCallId_GetDebugFutureThreadInfo = BIT(22), ///< SVC 0x2E (6.0.0+). Old: NpdmSystemCallId_GetFutureThreadInfo (5.0.0 - 5.1.0). + NpdmSystemCallId_GetLastThreadInfo = BIT(23), ///< SVC 0x2F. ///< System calls for index 2. - NpdmSystemCallId_GetResourceLimitLimitValue = BIT(0), - NpdmSystemCallId_GetResourceLimitCurrentValue = BIT(1), - NpdmSystemCallId_SetThreadActivity = BIT(2), - NpdmSystemCallId_GetThreadContext3 = BIT(3), - NpdmSystemCallId_WaitForAddress = BIT(4), - NpdmSystemCallId_SignalToAddress = BIT(5), - NpdmSystemCallId_SynchronizePreemptionState = BIT(6), - NpdmSystemCallId_Reserved2 = BIT(7), - NpdmSystemCallId_Reserved3 = BIT(8), - NpdmSystemCallId_Reserved4 = BIT(9), - NpdmSystemCallId_Reserved5 = BIT(10), - NpdmSystemCallId_Reserved6 = BIT(11), - NpdmSystemCallId_KernelDebug = BIT(12), - NpdmSystemCallId_ChangeKernelTraceState = BIT(13), - NpdmSystemCallId_Reserved7 = BIT(14), - NpdmSystemCallId_Reserved8 = BIT(15), - NpdmSystemCallId_CreateSession = BIT(16), - NpdmSystemCallId_AcceptSession = BIT(17), - NpdmSystemCallId_ReplyAndReceiveLight = BIT(18), - NpdmSystemCallId_ReplyAndReceive = BIT(19), - NpdmSystemCallId_ReplyAndReceiveWithUserBuffer = BIT(20), - NpdmSystemCallId_CreateEvent = BIT(21), - NpdmSystemCallId_Reserved9 = BIT(22), - NpdmSystemCallId_Reserved10 = BIT(23), + NpdmSystemCallId_GetResourceLimitLimitValue = BIT(0), ///< SVC 0x30. + NpdmSystemCallId_GetResourceLimitCurrentValue = BIT(1), ///< SVC 0x31. + NpdmSystemCallId_SetThreadActivity = BIT(2), ///< SVC 0x32. + NpdmSystemCallId_GetThreadContext3 = BIT(3), ///< SVC 0x33. + NpdmSystemCallId_WaitForAddress = BIT(4), ///< SVC 0x34 (4.0.0+). + NpdmSystemCallId_SignalToAddress = BIT(5), ///< SVC 0x35 (4.0.0+). + NpdmSystemCallId_SynchronizePreemptionState = BIT(6), ///< SVC 0x36 (8.0.0+). + NpdmSystemCallId_GetResourceLimitPeakValue = BIT(7), ///< SVC 0x37 (11.0.0+). + NpdmSystemCallId_Reserved2 = BIT(8), ///< SVC 0x38. + NpdmSystemCallId_CreateIoPool = BIT(9), ///< SVC 0x39 (13.0.0+). + NpdmSystemCallId_CreateIoRegion = BIT(10), ///< SVC 0x3A (13.0.0+). + NpdmSystemCallId_Reserved3 = BIT(11), ///< SVC 0x3B. + NpdmSystemCallId_KernelDebug = BIT(12), ///< SVC 0x3C (4.0.0+). Old: NpdmSystemCallId_DumpInfo (1.0.0 - 3.0.2). + NpdmSystemCallId_ChangeKernelTraceState = BIT(13), ///< SVC 0x3D (4.0.0+). + NpdmSystemCallId_Reserved4 = BIT(14), ///< SVC 0x3E. + NpdmSystemCallId_Reserved5 = BIT(15), ///< SVC 0x3F. + NpdmSystemCallId_CreateSession = BIT(16), ///< SVC 0x40. + NpdmSystemCallId_AcceptSession = BIT(17), ///< SVC 0x41. + NpdmSystemCallId_ReplyAndReceiveLight = BIT(18), ///< SVC 0x42. + NpdmSystemCallId_ReplyAndReceive = BIT(19), ///< SVC 0x43. + NpdmSystemCallId_ReplyAndReceiveWithUserBuffer = BIT(20), ///< SVC 0x44. + NpdmSystemCallId_CreateEvent = BIT(21), ///< SVC 0x45. + NpdmSystemCallId_MapIoRegion = BIT(22), ///< SVC 0x46 (13.0.0+). + NpdmSystemCallId_UnmapIoRegion = BIT(23), ///< SVC 0x47 (13.0.0+). ///< System calls for index 3. - NpdmSystemCallId_MapPhysicalMemoryUnsafe = BIT(0), - NpdmSystemCallId_UnmapPhysicalMemoryUnsafe = BIT(1), - NpdmSystemCallId_SetUnsafeLimit = BIT(2), - NpdmSystemCallId_CreateCodeMemory = BIT(3), - NpdmSystemCallId_ControlCodeMemory = BIT(4), - NpdmSystemCallId_SleepSystem = BIT(5), - NpdmSystemCallId_ReadWriteRegister = BIT(6), - NpdmSystemCallId_SetProcessActivity = BIT(7), - NpdmSystemCallId_CreateSharedMemory = BIT(8), - NpdmSystemCallId_MapTransferMemory = BIT(9), - NpdmSystemCallId_UnmapTransferMemory = BIT(10), - NpdmSystemCallId_CreateInterruptEvent = BIT(11), - NpdmSystemCallId_QueryPhysicalAddress = BIT(12), - NpdmSystemCallId_QueryIoMapping = BIT(13), - NpdmSystemCallId_CreateDeviceAddressSpace = BIT(14), - NpdmSystemCallId_AttachDeviceAddressSpace = BIT(15), - NpdmSystemCallId_DetachDeviceAddressSpace = BIT(16), - NpdmSystemCallId_MapDeviceAddressSpaceByForce = BIT(17), - NpdmSystemCallId_MapDeviceAddressSpaceAligned = BIT(18), - NpdmSystemCallId_MapDeviceAddressSpace = BIT(19), - NpdmSystemCallId_UnmapDeviceAddressSpace = BIT(20), - NpdmSystemCallId_InvalidateProcessDataCache = BIT(21), - NpdmSystemCallId_StoreProcessDataCache = BIT(22), - NpdmSystemCallId_FlushProcessDataCache = BIT(23), + NpdmSystemCallId_MapPhysicalMemoryUnsafe = BIT(0), ///< SVC 0x48 (5.0.0+). + NpdmSystemCallId_UnmapPhysicalMemoryUnsafe = BIT(1), ///< SVC 0x49 (5.0.0+). + NpdmSystemCallId_SetUnsafeLimit = BIT(2), ///< SVC 0x4A (5.0.0+). + NpdmSystemCallId_CreateCodeMemory = BIT(3), ///< SVC 0x4B (4.0.0+). + NpdmSystemCallId_ControlCodeMemory = BIT(4), ///< SVC 0x4C (4.0.0+). + NpdmSystemCallId_SleepSystem = BIT(5), ///< SVC 0x4D. + NpdmSystemCallId_ReadWriteRegister = BIT(6), ///< SVC 0x4E. + NpdmSystemCallId_SetProcessActivity = BIT(7), ///< SVC 0x4F. + NpdmSystemCallId_CreateSharedMemory = BIT(8), ///< SVC 0x50. + NpdmSystemCallId_MapTransferMemory = BIT(9), ///< SVC 0x51. + NpdmSystemCallId_UnmapTransferMemory = BIT(10), ///< SVC 0x52. + NpdmSystemCallId_CreateInterruptEvent = BIT(11), ///< SVC 0x53. + NpdmSystemCallId_QueryPhysicalAddress = BIT(12), ///< SVC 0x54. + NpdmSystemCallId_QueryIoMapping = BIT(13), ///< SVC 0x55. + NpdmSystemCallId_CreateDeviceAddressSpace = BIT(14), ///< SVC 0x56. + NpdmSystemCallId_AttachDeviceAddressSpace = BIT(15), ///< SVC 0x57. + NpdmSystemCallId_DetachDeviceAddressSpace = BIT(16), ///< SVC 0x58. + NpdmSystemCallId_MapDeviceAddressSpaceByForce = BIT(17), ///< SVC 0x59. + NpdmSystemCallId_MapDeviceAddressSpaceAligned = BIT(18), ///< SVC 0x5A. + NpdmSystemCallId_MapDeviceAddressSpace = BIT(19), ///< SVC 0x5B (1.0.0 - 12.1.0). + NpdmSystemCallId_UnmapDeviceAddressSpace = BIT(20), ///< SVC 0x5C. + NpdmSystemCallId_InvalidateProcessDataCache = BIT(21), ///< SVC 0x5D. + NpdmSystemCallId_StoreProcessDataCache = BIT(22), ///< SVC 0x5E. + NpdmSystemCallId_FlushProcessDataCache = BIT(23), ///< SVC 0x5F. ///< System calls for index 4. - NpdmSystemCallId_DebugActiveProcess = BIT(0), - NpdmSystemCallId_BreakDebugProcess = BIT(1), - NpdmSystemCallId_TerminateDebugProcess = BIT(2), - NpdmSystemCallId_GetDebugEvent = BIT(3), - NpdmSystemCallId_ContinueDebugEvent = BIT(4), - NpdmSystemCallId_GetProcessList = BIT(5), - NpdmSystemCallId_GetThreadList = BIT(6), - NpdmSystemCallId_GetDebugThreadContext = BIT(7), - NpdmSystemCallId_SetDebugThreadContext = BIT(8), - NpdmSystemCallId_QueryDebugProcessMemory = BIT(9), - NpdmSystemCallId_ReadDebugProcessMemory = BIT(10), - NpdmSystemCallId_WriteDebugProcessMemory = BIT(11), - NpdmSystemCallId_SetHardwareBreakPoint = BIT(12), - NpdmSystemCallId_GetDebugThreadParam = BIT(13), - NpdmSystemCallId_Reserved11 = BIT(14), - NpdmSystemCallId_GetSystemInfo = BIT(15), - NpdmSystemCallId_CreatePort = BIT(16), - NpdmSystemCallId_ManageNamedPort = BIT(17), - NpdmSystemCallId_ConnectToPort = BIT(18), - NpdmSystemCallId_SetProcessMemoryPermission = BIT(19), - NpdmSystemCallId_MapProcessMemory = BIT(20), - NpdmSystemCallId_UnmapProcessMemory = BIT(21), - NpdmSystemCallId_QueryProcessMemory = BIT(22), - NpdmSystemCallId_MapProcessCodeMemory = BIT(23), + NpdmSystemCallId_DebugActiveProcess = BIT(0), ///< SVC 0x60. + NpdmSystemCallId_BreakDebugProcess = BIT(1), ///< SVC 0x61. + NpdmSystemCallId_TerminateDebugProcess = BIT(2), ///< SVC 0x62. + NpdmSystemCallId_GetDebugEvent = BIT(3), ///< SVC 0x63. + NpdmSystemCallId_ContinueDebugEvent = BIT(4), ///< SVC 0x64. + NpdmSystemCallId_GetProcessList = BIT(5), ///< SVC 0x65. + NpdmSystemCallId_GetThreadList = BIT(6), ///< SVC 0x66. + NpdmSystemCallId_GetDebugThreadContext = BIT(7), ///< SVC 0x67. + NpdmSystemCallId_SetDebugThreadContext = BIT(8), ///< SVC 0x68. + NpdmSystemCallId_QueryDebugProcessMemory = BIT(9), ///< SVC 0x69. + NpdmSystemCallId_ReadDebugProcessMemory = BIT(10), ///< SVC 0x6A. + NpdmSystemCallId_WriteDebugProcessMemory = BIT(11), ///< SVC 0x6B. + NpdmSystemCallId_SetHardwareBreakPoint = BIT(12), ///< SVC 0x6C. + NpdmSystemCallId_GetDebugThreadParam = BIT(13), ///< SVC 0x6D. + NpdmSystemCallId_Reserved6 = BIT(14), ///< SVC 0x6E. + NpdmSystemCallId_GetSystemInfo = BIT(15), ///< SVC 0x6F (5.0.0+). + NpdmSystemCallId_CreatePort = BIT(16), ///< SVC 0x70. + NpdmSystemCallId_ManageNamedPort = BIT(17), ///< SVC 0x71. + NpdmSystemCallId_ConnectToPort = BIT(18), ///< SVC 0x72. + NpdmSystemCallId_SetProcessMemoryPermission = BIT(19), ///< SVC 0x73. + NpdmSystemCallId_MapProcessMemory = BIT(20), ///< SVC 0x74. + NpdmSystemCallId_UnmapProcessMemory = BIT(21), ///< SVC 0x75. + NpdmSystemCallId_QueryProcessMemory = BIT(22), ///< SVC 0x76. + NpdmSystemCallId_MapProcessCodeMemory = BIT(23), ///< SVC 0x77. ///< System calls for index 5. - NpdmSystemCallId_UnmapProcessCodeMemory = BIT(0), - NpdmSystemCallId_CreateProcess = BIT(1), - NpdmSystemCallId_StartProcess = BIT(2), - NpdmSystemCallId_TerminateProcess = BIT(3), - NpdmSystemCallId_GetProcessInfo = BIT(4), - NpdmSystemCallId_CreateResourceLimit = BIT(5), - NpdmSystemCallId_SetResourceLimitLimitValue = BIT(6), - NpdmSystemCallId_CallSecureMonitor = BIT(7), + NpdmSystemCallId_UnmapProcessCodeMemory = BIT(0), ///< SVC 0x78. + NpdmSystemCallId_CreateProcess = BIT(1), ///< SVC 0x79. + NpdmSystemCallId_StartProcess = BIT(2), ///< SVC 0x7A. + NpdmSystemCallId_TerminateProcess = BIT(3), ///< SVC 0x7B. + NpdmSystemCallId_GetProcessInfo = BIT(4), ///< SVC 0x7C. + NpdmSystemCallId_CreateResourceLimit = BIT(5), ///< SVC 0x7D. + NpdmSystemCallId_SetResourceLimitLimitValue = BIT(6), ///< SVC 0x7E. + NpdmSystemCallId_CallSecureMonitor = BIT(7), ///< SVC 0x7F. + NpdmSystemCallId_Reserved7 = BIT(8), ///< SVC 0x80. + NpdmSystemCallId_Reserved8 = BIT(9), ///< SVC 0x81. + NpdmSystemCallId_Reserved9 = BIT(10), ///< SVC 0x82. + NpdmSystemCallId_Reserved10 = BIT(11), ///< SVC 0x83. + NpdmSystemCallId_Reserved11 = BIT(12), ///< SVC 0x84. + NpdmSystemCallId_Reserved12 = BIT(13), ///< SVC 0x85. + NpdmSystemCallId_Reserved13 = BIT(14), ///< SVC 0x86. + NpdmSystemCallId_Reserved14 = BIT(15), ///< SVC 0x87. + NpdmSystemCallId_Reserved15 = BIT(16), ///< SVC 0x88. + NpdmSystemCallId_Reserved16 = BIT(17), ///< SVC 0x89. + NpdmSystemCallId_Reserved17 = BIT(18), ///< SVC 0x8A. + NpdmSystemCallId_Reserved18 = BIT(19), ///< SVC 0x8B. + NpdmSystemCallId_Reserved19 = BIT(20), ///< SVC 0x8C. + NpdmSystemCallId_Reserved20 = BIT(21), ///< SVC 0x8D. + NpdmSystemCallId_Reserved21 = BIT(22), ///< SVC 0x8E. + NpdmSystemCallId_Reserved22 = BIT(23), ///< SVC 0x8F. + + ///< System calls for index 6. + NpdmSystemCallId_MapInsecureMemory = BIT(0), ///< SVC 0x90 (15.0.0+). + NpdmSystemCallId_UnmapInsecureMemory = BIT(1), ///< SVC 0x91 (15.0.0+). + NpdmSystemCallId_Reserved23 = BIT(2), ///< SVC 0x92. + NpdmSystemCallId_Reserved24 = BIT(3), ///< SVC 0x93. + NpdmSystemCallId_Reserved25 = BIT(4), ///< SVC 0x94. + NpdmSystemCallId_Reserved26 = BIT(5), ///< SVC 0x95. + NpdmSystemCallId_Reserved27 = BIT(6), ///< SVC 0x96. + NpdmSystemCallId_Reserved28 = BIT(7), ///< SVC 0x97. + NpdmSystemCallId_Reserved29 = BIT(8), ///< SVC 0x98. + NpdmSystemCallId_Reserved30 = BIT(9), ///< SVC 0x99. + NpdmSystemCallId_Reserved31 = BIT(10), ///< SVC 0x9A. + NpdmSystemCallId_Reserved32 = BIT(11), ///< SVC 0x9B. + NpdmSystemCallId_Reserved33 = BIT(12), ///< SVC 0x9C. + NpdmSystemCallId_Reserved34 = BIT(13), ///< SVC 0x9D. + NpdmSystemCallId_Reserved35 = BIT(14), ///< SVC 0x9E. + NpdmSystemCallId_Reserved36 = BIT(15), ///< SVC 0x9F. + NpdmSystemCallId_Reserved37 = BIT(16), ///< SVC 0xA0. + NpdmSystemCallId_Reserved38 = BIT(17), ///< SVC 0xA1. + NpdmSystemCallId_Reserved39 = BIT(18), ///< SVC 0xA2. + NpdmSystemCallId_Reserved40 = BIT(19), ///< SVC 0xA3. + NpdmSystemCallId_Reserved41 = BIT(20), ///< SVC 0xA4. + NpdmSystemCallId_Reserved42 = BIT(21), ///< SVC 0xA5. + NpdmSystemCallId_Reserved43 = BIT(22), ///< SVC 0xA6. + NpdmSystemCallId_Reserved44 = BIT(23), ///< SVC 0xA7. + + ///< System calls for index 7. + NpdmSystemCallId_Reserved45 = BIT(0), ///< SVC 0xA8. + NpdmSystemCallId_Reserved46 = BIT(1), ///< SVC 0xA9. + NpdmSystemCallId_Reserved47 = BIT(2), ///< SVC 0xAA. + NpdmSystemCallId_Reserved48 = BIT(3), ///< SVC 0xAB. + NpdmSystemCallId_Reserved49 = BIT(4), ///< SVC 0xAC. + NpdmSystemCallId_Reserved50 = BIT(5), ///< SVC 0xAD. + NpdmSystemCallId_Reserved51 = BIT(6), ///< SVC 0xAE. + NpdmSystemCallId_Reserved52 = BIT(7), ///< SVC 0xAF. + NpdmSystemCallId_Reserved53 = BIT(8), ///< SVC 0xB0. + NpdmSystemCallId_Reserved54 = BIT(9), ///< SVC 0xB1. + NpdmSystemCallId_Reserved55 = BIT(10), ///< SVC 0xB2. + NpdmSystemCallId_Reserved56 = BIT(11), ///< SVC 0xB3. + NpdmSystemCallId_Reserved57 = BIT(12), ///< SVC 0xB4. + NpdmSystemCallId_Reserved58 = BIT(13), ///< SVC 0xB5. + NpdmSystemCallId_Reserved59 = BIT(14), ///< SVC 0xB6. + NpdmSystemCallId_Reserved60 = BIT(15), ///< SVC 0xB7. + NpdmSystemCallId_Reserved61 = BIT(16), ///< SVC 0xB8. + NpdmSystemCallId_Reserved62 = BIT(17), ///< SVC 0xB9. + NpdmSystemCallId_Reserved63 = BIT(18), ///< SVC 0xBA. + NpdmSystemCallId_Reserved64 = BIT(19), ///< SVC 0xBB. + NpdmSystemCallId_Reserved65 = BIT(20), ///< SVC 0xBC. + NpdmSystemCallId_Reserved66 = BIT(21), ///< SVC 0xBD. + NpdmSystemCallId_Reserved67 = BIT(22), ///< SVC 0xBE. + NpdmSystemCallId_Reserved68 = BIT(23), ///< SVC 0xBF. NpdmSystemCallId_Count = 0xC0 ///< Total values supported by this enum. } NpdmSystemCallId; @@ -521,12 +593,12 @@ typedef enum { typedef struct { u32 entry_value : NpdmKernelCapabilityEntryNumber_MemoryRegionMap; ///< Always set to NpdmKernelCapabilityEntryValue_MemoryRegionMap. u32 padding : 1; ///< Always set to zero. + u32 region_type_0 : 6; ///< NpdmRegionType. + u32 permission_type_0 : 1; ///< NpdmPermissionType. u32 region_type_1 : 6; ///< NpdmRegionType. u32 permission_type_1 : 1; ///< NpdmPermissionType. u32 region_type_2 : 6; ///< NpdmRegionType. u32 permission_type_2 : 1; ///< NpdmPermissionType. - u32 region_type_3 : 6; ///< NpdmRegionType. - u32 permission_type_3 : 1; ///< NpdmPermissionType. } NpdmMemoryRegionMap; NXDT_ASSERT(NpdmMemoryRegionMap, 0x4); @@ -535,8 +607,8 @@ NXDT_ASSERT(NpdmMemoryRegionMap, 0x4); typedef struct { u32 entry_value : NpdmKernelCapabilityEntryNumber_EnableInterrupts; ///< Always set to NpdmKernelCapabilityEntryValue_EnableInterrupts. u32 padding : 1; ///< Always set to zero. + u32 interrupt_number_0 : 10; ///< 0x3FF means empty. u32 interrupt_number_1 : 10; ///< 0x3FF means empty. - u32 interrupt_number_2 : 10; ///< 0x3FF means empty. } NpdmEnableInterrupts; NXDT_ASSERT(NpdmEnableInterrupts, 0x4); @@ -559,11 +631,12 @@ typedef struct { NXDT_ASSERT(NpdmMiscParams, 0x4); /// KernelVersion entry for the KernelCapability descriptor. +/// This is derived from/equivalent to SDK version. typedef struct { u32 entry_value : NpdmKernelCapabilityEntryNumber_KernelVersion; ///< Always set to NpdmKernelCapabilityEntryValue_KernelVersion. u32 padding : 1; ///< Always set to zero. - u32 minor_version : 4; - u32 major_version : 13; + u32 minor_version : 4; ///< SDK minor version. + u32 major_version : 13; ///< SDK major version + 4. } NpdmKernelVersion; NXDT_ASSERT(NpdmKernelVersion, 0x4); diff --git a/include/core/title.h b/include/core/title.h index 853fa06..6c567e5 100644 --- a/include/core/title.h +++ b/include/core/title.h @@ -46,6 +46,10 @@ typedef struct { } TitleApplicationMetadata; /// Generated using ncm calls. +/// User applications: the parent pointer is always unused. The previous/next pointers reference other user applications with the same ID. +/// Patches: the parent pointer always references the first corresponding user application. The previous/next pointers reference other patches with the same ID. +/// Add-on contents: the parent pointer always references the first corresponding user application. The previous/next pointers reference sibling add-on contents. +/// Add-on content patches: the parent pointer always references the first corresponding add-on content. The previous/next pointers reference other patches with the same ID. typedef struct _TitleInfo { u8 storage_id; ///< NcmStorageId. NcmContentMetaKey meta_key; ///< Used with ncm calls. @@ -55,7 +59,7 @@ typedef struct _TitleInfo { u64 size; ///< Total title size. char size_str[32]; ///< Total title size string. TitleApplicationMetadata *app_metadata; ///< User application metadata. - struct _TitleInfo *parent, *previous, *next; ///< Used with TitleInfo entries from user applications, patches and add-on contents. The parent pointer is unused in user applications. + struct _TitleInfo *parent, *previous, *next; ///< Linked lists. } TitleInfo; /// Used to deal with user applications stored in the eMMC, SD card and/or gamecard. @@ -152,18 +156,6 @@ const char *titleGetNcmContentMetaTypeName(u8 content_meta_type); /// Miscellaneous functions. -NX_INLINE void titleConvertNcmContentSizeToU64(const u8 *size, u64 *out) -{ - if (!size || !out) return; - *out = 0; - memcpy(out, size, 6); -} - -NX_INLINE void titleConvertU64ToNcmContentSize(const u64 *size, u8 *out) -{ - if (size && out) memcpy(out, size, 6); -} - NX_INLINE u64 titleGetPatchIdByApplicationId(u64 app_id) { return (app_id + TITLE_PATCH_TYPE_VALUE); @@ -184,7 +176,7 @@ NX_INLINE u64 titleGetAddOnContentBaseIdByApplicationId(u64 app_id) return ((app_id & TITLE_ADDONCONTENT_CONVERSION_MASK) + TITLE_ADDONCONTENT_TYPE_VALUE); } -NX_INLINE u64 titleGetAddOnContentIdWithIndexByApplicationId(u64 app_id, u16 idx) +NX_INLINE u64 titleGetAddOnContentIdByApplicationIdAndIndex(u64 app_id, u16 idx) { return (titleGetAddOnContentBaseIdByApplicationId(app_id) + idx + 1); } @@ -233,6 +225,31 @@ NX_INLINE bool titleCheckIfDeltaIdBelongsToApplicationId(u64 app_id, u64 delta_i return (delta_id == titleGetDeltaIdByApplicationId(app_id)); } +NX_INLINE u64 titleGetDataPatchIdByAddOnContentId(u64 aoc_id) +{ + return (aoc_id + TITLE_PATCH_TYPE_VALUE); +} + +NX_INLINE u64 titleGetAddOnContentIdByDataPatchId(u64 data_patch_id) +{ + return (data_patch_id - TITLE_PATCH_TYPE_VALUE); +} + +NX_INLINE bool titleCheckIfDataPatchIdBelongsToAddOnContentId(u64 aoc_id, u64 data_patch_id) +{ + return (data_patch_id == titleGetDataPatchIdByAddOnContentId(aoc_id)); +} + +NX_INLINE u64 titleGetApplicationIdByDataPatchId(u64 data_patch_id) +{ + return titleGetApplicationIdByAddOnContentId(titleGetAddOnContentIdByDataPatchId(data_patch_id)); +} + +NX_INLINE bool titleCheckIfDataPatchIdBelongsToApplicationId(u64 app_id, u64 data_patch_id) +{ + return titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, titleGetAddOnContentIdByDataPatchId(data_patch_id)); +} + NX_INLINE u32 titleGetContentCountByType(TitleInfo *info, u8 content_type) { if (!info || !info->content_count || !info->content_infos || content_type > NcmContentType_DeltaFragment) return 0; diff --git a/source/core/bktr.c b/source/core/bktr.c index d471b13..5fa4761 100644 --- a/source/core/bktr.c +++ b/source/core/bktr.c @@ -87,7 +87,7 @@ static bool bktrReadAesCtrExStorage(BucketTreeVisitor *visitor, void *out, u64 r static bool bktrReadCompressedStorage(BucketTreeVisitor *visitor, void *out, u64 read_size, u64 offset); static bool bktrReadSubStorage(BucketTreeSubStorage *substorage, BucketTreeSubStorageReadParams *params); -NX_INLINE void bktrBucketInitializeSubStorageReadParams(BucketTreeSubStorageReadParams *out, void *buffer, u64 offset, u64 size, u64 virtual_offset, u32 ctr_val, bool aes_ctr_ex_crypt, u8 parent_storage_type); +NX_INLINE void bktrInitializeSubStorageReadParams(BucketTreeSubStorageReadParams *out, void *buffer, u64 offset, u64 size, u64 virtual_offset, u32 ctr_val, bool aes_ctr_ex_crypt, u8 parent_storage_type); static bool bktrVerifyBucketInfo(NcaBucketInfo *bucket, u64 node_size, u64 entry_size, u64 *out_node_storage_size, u64 *out_entry_storage_size); static bool bktrValidateTableOffsetNode(const BucketTreeTable *table, u64 node_size, u64 entry_size, u32 entry_count, u64 *out_start_offset, u64 *out_end_offset); @@ -189,7 +189,7 @@ bool bktrInitializeCompressedStorageContext(BucketTreeContext *out, BucketTreeSu BucketTreeTable *compressed_table = NULL; u64 node_storage_size = 0, entry_storage_size = 0; BucketTreeSubStorageReadParams params = {0}; - bool success = false; + bool dump_table = false, success = false; /* Verify bucket info. */ if (!bktrVerifyBucketInfo(compressed_bucket, BKTR_NODE_SIZE, BKTR_COMPRESSED_ENTRY_SIZE, &node_storage_size, &entry_storage_size)) @@ -208,7 +208,7 @@ bool bktrInitializeCompressedStorageContext(BucketTreeContext *out, BucketTreeSu /* Read Compressed storage table data. */ const u64 compression_table_offset = (nca_fs_ctx->hash_region.size + compressed_bucket->offset); - bktrBucketInitializeSubStorageReadParams(¶ms, compressed_table, compression_table_offset, compressed_bucket->size, 0, 0, false, BucketTreeSubStorageType_Compressed); + bktrInitializeSubStorageReadParams(¶ms, compressed_table, compression_table_offset, compressed_bucket->size, 0, 0, false, BucketTreeSubStorageType_Compressed); if (!bktrReadSubStorage(substorage, ¶ms)) { @@ -216,6 +216,8 @@ bool bktrInitializeCompressedStorageContext(BucketTreeContext *out, BucketTreeSu goto end; } + dump_table = true; + /* Validate table offset node. */ u64 start_offset = 0, end_offset = 0; if (!bktrValidateTableOffsetNode(compressed_table, BKTR_NODE_SIZE, BKTR_COMPRESSED_ENTRY_SIZE, compressed_bucket->header.entry_count, &start_offset, &end_offset)) @@ -243,7 +245,16 @@ bool bktrInitializeCompressedStorageContext(BucketTreeContext *out, BucketTreeSu success = true; end: - if (!success && compressed_table) free(compressed_table); + if (!success) + { + LOG_DATA_DEBUG(compressed_bucket, sizeof(NcaBucketInfo), "Compressed Storage BucketInfo dump:"); + + if (compressed_table) + { + if (dump_table) LOG_DATA_DEBUG(compressed_table, compressed_bucket->size, "Compressed Storage Table dump:"); + free(compressed_table); + } + } return success; } @@ -508,7 +519,7 @@ static bool bktrInitializeIndirectStorageContext(BucketTreeContext *out, NcaFsSe NcaBucketInfo *indirect_bucket = (is_sparse ? &(nca_fs_ctx->header.sparse_info.bucket) : &(nca_fs_ctx->header.patch_info.indirect_bucket)); BucketTreeTable *indirect_table = NULL; u64 node_storage_size = 0, entry_storage_size = 0; - bool success = false; + bool dump_table = false, success = false; /* Verify bucket info. */ if (!bktrVerifyBucketInfo(indirect_bucket, BKTR_NODE_SIZE, BKTR_INDIRECT_ENTRY_SIZE, &node_storage_size, &entry_storage_size)) @@ -556,6 +567,8 @@ static bool bktrInitializeIndirectStorageContext(BucketTreeContext *out, NcaFsSe aes128CtrCrypt(&sparse_ctr_ctx, indirect_table, indirect_table, indirect_bucket->size); } + dump_table = true; + /* Validate table offset node. */ u64 start_offset = 0, end_offset = 0; if (!bktrValidateTableOffsetNode(indirect_table, BKTR_NODE_SIZE, BKTR_INDIRECT_ENTRY_SIZE, indirect_bucket->header.entry_count, &start_offset, &end_offset)) @@ -581,7 +594,16 @@ static bool bktrInitializeIndirectStorageContext(BucketTreeContext *out, NcaFsSe success = true; end: - if (!success && indirect_table) free(indirect_table); + if (!success) + { + LOG_DATA_DEBUG(indirect_bucket, sizeof(NcaBucketInfo), "Indirect Storage BucketInfo dump (%s):", is_sparse ? "sparse" : "patch"); + + if (indirect_table) + { + if (dump_table) LOG_DATA_DEBUG(indirect_table, indirect_bucket->size, "Indirect Storage Table dump (%s):", is_sparse ? "sparse" : "patch"); + free(indirect_table); + } + } return success; } @@ -655,14 +677,14 @@ static bool bktrReadIndirectStorage(BucketTreeVisitor *visitor, void *out, u64 r /* Read only within the current indirect storage entry. */ BucketTreeSubStorageReadParams params = {0}; const u64 data_offset = (offset - cur_entry_offset + cur_entry.physical_offset); - bktrBucketInitializeSubStorageReadParams(¶ms, out, data_offset, read_size, offset, 0, false, ctx->storage_type); + bktrInitializeSubStorageReadParams(¶ms, out, data_offset, read_size, offset, 0, false, ctx->storage_type); if (cur_entry.storage_index == BucketTreeIndirectStorageIndex_Original) { if (!missing_original_storage) { /* Retrieve data from the original data storage. */ - /* This may either be a Regular/Sparse/Compressed storage from the base NCA (Indirect) or a Regular storage from this very same NCA (Sparse). */ + /* This must either be a Regular/Sparse/Compressed storage from the base NCA (Indirect) or a Regular storage from this very same NCA (Sparse). */ success = bktrReadSubStorage(&(ctx->substorages[0]), ¶ms); if (!success) LOG_MSG_ERROR("Failed to read 0x%lX-byte long chunk from offset 0x%lX in original data storage!", read_size, data_offset); } else { @@ -708,7 +730,7 @@ static bool bktrInitializeAesCtrExStorageContext(BucketTreeContext *out, NcaFsSe NcaBucketInfo *aes_ctr_ex_bucket = &(nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket); BucketTreeTable *aes_ctr_ex_table = NULL; u64 node_storage_size = 0, entry_storage_size = 0; - bool success = false; + bool dump_table = false, success = false; /* Verify bucket info. */ if (!bktrVerifyBucketInfo(aes_ctr_ex_bucket, BKTR_NODE_SIZE, BKTR_AES_CTR_EX_ENTRY_SIZE, &node_storage_size, &entry_storage_size)) @@ -732,6 +754,8 @@ static bool bktrInitializeAesCtrExStorageContext(BucketTreeContext *out, NcaFsSe goto end; } + dump_table = true; + /* Validate table offset node. */ u64 start_offset = 0, end_offset = 0; if (!bktrValidateTableOffsetNode(aes_ctr_ex_table, BKTR_NODE_SIZE, BKTR_AES_CTR_EX_ENTRY_SIZE, aes_ctr_ex_bucket->header.entry_count, &start_offset, &end_offset)) @@ -757,7 +781,16 @@ static bool bktrInitializeAesCtrExStorageContext(BucketTreeContext *out, NcaFsSe success = true; end: - if (!success && aes_ctr_ex_table) free(aes_ctr_ex_table); + if (!success) + { + LOG_DATA_DEBUG(aes_ctr_ex_bucket, sizeof(NcaBucketInfo), "AesCtrEx Storage BucketInfo dump:"); + + if (aes_ctr_ex_table) + { + if (dump_table) LOG_DATA_DEBUG(aes_ctr_ex_table, aes_ctr_ex_bucket->size, "AesCtrEx Storage Table dump:"); + free(aes_ctr_ex_table); + } + } return success; } @@ -825,7 +858,7 @@ static bool bktrReadAesCtrExStorage(BucketTreeVisitor *visitor, void *out, u64 r { /* Read only within the current AesCtrEx storage entry. */ BucketTreeSubStorageReadParams params = {0}; - bktrBucketInitializeSubStorageReadParams(¶ms, out, offset, read_size, 0, cur_entry.generation, cur_entry.encryption == BucketTreeAesCtrExStorageEncryption_Enabled, ctx->storage_type); + bktrInitializeSubStorageReadParams(¶ms, out, offset, read_size, 0, cur_entry.generation, cur_entry.encryption == BucketTreeAesCtrExStorageEncryption_Enabled, ctx->storage_type); success = bktrReadSubStorage(&(ctx->substorages[0]), ¶ms); if (!success) LOG_MSG_ERROR("Failed to read 0x%lX-byte long chunk at offset 0x%lX from AesCtrEx storage!", read_size, offset); @@ -930,7 +963,7 @@ static bool bktrReadCompressedStorage(BucketTreeVisitor *visitor, void *out, u64 /* We can randomly access data that's not compressed. */ /* Let's just read what we need. */ const u64 data_offset = (compressed_storage_base_offset + (offset - cur_entry_offset + (u64)cur_entry.physical_offset)); - bktrBucketInitializeSubStorageReadParams(¶ms, out, data_offset, read_size, 0, 0, false, ctx->storage_type); + bktrInitializeSubStorageReadParams(¶ms, out, data_offset, read_size, 0, 0, false, ctx->storage_type); success = bktrReadSubStorage(&(ctx->substorages[0]), ¶ms); if (!success) LOG_MSG_ERROR("Failed to read 0x%lX-byte long chunk from offset 0x%lX in non-compressed entry!", read_size, data_offset); @@ -963,7 +996,7 @@ static bool bktrReadCompressedStorage(BucketTreeVisitor *visitor, void *out, u64 /* Adjust read pointer. This will let us use the same buffer for storing read data and decompressing it. */ read_ptr = (buffer + (buffer_size - compressed_data_size)); - bktrBucketInitializeSubStorageReadParams(¶ms, read_ptr, data_offset, compressed_data_size, 0, 0, false, ctx->storage_type); + bktrInitializeSubStorageReadParams(¶ms, read_ptr, data_offset, compressed_data_size, 0, 0, false, ctx->storage_type); /* Read compressed LZ4 block. */ if (!bktrReadSubStorage(&(ctx->substorages[0]), ¶ms)) @@ -1045,7 +1078,7 @@ static bool bktrReadSubStorage(BucketTreeSubStorage *substorage, BucketTreeSubSt return success; } -NX_INLINE void bktrBucketInitializeSubStorageReadParams(BucketTreeSubStorageReadParams *out, void *buffer, u64 offset, u64 size, u64 virtual_offset, u32 ctr_val, bool aes_ctr_ex_crypt, u8 parent_storage_type) +NX_INLINE void bktrInitializeSubStorageReadParams(BucketTreeSubStorageReadParams *out, void *buffer, u64 offset, u64 size, u64 virtual_offset, u32 ctr_val, bool aes_ctr_ex_crypt, u8 parent_storage_type) { out->buffer = buffer; out->offset = offset; diff --git a/source/core/cnmt.c b/source/core/cnmt.c index 92d45d8..09b1a20 100644 --- a/source/core/cnmt.c +++ b/source/core/cnmt.c @@ -59,7 +59,7 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx) u8 content_meta_type = 0; u64 title_id = 0, cur_offset = 0; - bool success = false, invalid_ext_header_size = false, invalid_ext_data_size = false, dump_packaged_header = false; + bool success = false, invalid_ext_header_size = false, invalid_ext_data_size = false, dump_cnmt = false; /* Free output context beforehand. */ cnmtFreeContext(out); @@ -125,6 +125,8 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx) goto end; } + dump_cnmt = true; + /* Calculate SHA-256 checksum for the whole raw CNMT. */ sha256CalculateHash(out->raw_data_hash, out->raw_data, out->raw_data_size); @@ -135,21 +137,18 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx) if (out->packaged_header->title_id != title_id) { LOG_MSG_ERROR("CNMT title ID mismatch! (%016lX != %016lX).", out->packaged_header->title_id, title_id); - dump_packaged_header = true; goto end; } if (out->packaged_header->content_meta_type != content_meta_type) { LOG_MSG_ERROR("CNMT content meta type mismatch! (0x%02X != 0x%02X).", out->packaged_header->content_meta_type, content_meta_type); - dump_packaged_header = true; goto end; } if (!out->packaged_header->content_count && out->packaged_header->content_meta_type != NcmContentMetaType_SystemUpdate) { LOG_MSG_ERROR("Invalid content count!"); - dump_packaged_header = true; goto end; } @@ -157,7 +156,6 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx) (out->packaged_header->content_meta_type != NcmContentMetaType_SystemUpdate && out->packaged_header->content_meta_count)) { LOG_MSG_ERROR("Invalid content meta count!"); - dump_packaged_header = true; goto end; } @@ -183,29 +181,33 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx) invalid_ext_data_size = (out->extended_data_size <= sizeof(ContentMetaPatchMetaExtendedDataHeader)); break; case NcmContentMetaType_AddOnContent: - invalid_ext_header_size = (out->packaged_header->extended_header_size != (u16)sizeof(ContentMetaAddOnContentMetaExtendedHeader)); + invalid_ext_header_size = (out->packaged_header->extended_header_size != (u16)sizeof(ContentMetaAddOnContentMetaExtendedHeader) && \ + out->packaged_header->extended_header_size != (u16)sizeof(ContentMetaLegacyAddOnContentMetaExtendedHeader)); break; case NcmContentMetaType_Delta: invalid_ext_header_size = (out->packaged_header->extended_header_size != (u16)sizeof(ContentMetaDeltaMetaExtendedHeader)); out->extended_data_size = (!invalid_ext_header_size ? ((ContentMetaDeltaMetaExtendedHeader*)out->extended_header)->extended_data_size : 0); invalid_ext_data_size = (out->extended_data_size <= sizeof(ContentMetaDeltaMetaExtendedDataHeader)); break; + case NcmContentMetaType_DataPatch: + invalid_ext_header_size = (out->packaged_header->extended_header_size != (u16)sizeof(ContentMetaDataPatchMetaExtendedHeader)); + out->extended_data_size = (!invalid_ext_header_size ? ((ContentMetaDataPatchMetaExtendedHeader*)out->extended_header)->extended_data_size : 0); + invalid_ext_data_size = (out->extended_data_size <= sizeof(ContentMetaPatchMetaExtendedDataHeader)); + break; default: - invalid_ext_header_size = (out->packaged_header->extended_header_size > 0); + invalid_ext_header_size = (out->packaged_header->extended_header_size != 0); break; } if (invalid_ext_header_size) { LOG_MSG_ERROR("Invalid extended header size!"); - dump_packaged_header = true; goto end; } if (invalid_ext_data_size) { LOG_DATA_ERROR(out->extended_header, out->packaged_header->extended_header_size, "Invalid extended data size! CNMT Extended Header dump:"); - dump_packaged_header = true; goto end; } } @@ -254,7 +256,7 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx) end: if (!success) { - if (dump_packaged_header) LOG_DATA_DEBUG(out->packaged_header, sizeof(ContentMetaPackagedContentMetaHeader), "CNMT Packaged Header dump:"); + if (dump_cnmt) LOG_DATA_DEBUG(out->raw_data, out->raw_data_size, "Raw CNMT dump:"); cnmtFreeContext(out); } @@ -280,7 +282,7 @@ bool cnmtUpdateContentInfo(ContentMetaContext *cnmt_ctx, NcaContext *nca_ctx) NcmContentInfo *content_info = &(packaged_content_info->info); u64 content_size = 0; - titleConvertNcmContentSizeToU64(content_info->size, &content_size); + ncmContentInfoSizeToU64(content_info, &content_size); if (content_size == nca_ctx->content_size && content_info->content_type == nca_ctx->content_type && content_info->id_offset == nca_ctx->id_offset) { @@ -361,7 +363,7 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_ char *xml_buf = NULL; u64 xml_buf_size = 0; char digest_str[0x41] = {0}; - u8 count = 0; + u8 count = 0, content_meta_type = cnmt_ctx->packaged_header->content_meta_type; bool success = false, invalid_nca = false; /* Free AuthoringTool-like XML data if needed. */ @@ -376,7 +378,7 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_ " %u\n" \ " %u\n" \ " %u\n", \ - titleGetNcmContentMetaTypeName(cnmt_ctx->packaged_header->content_meta_type), \ + titleGetNcmContentMetaTypeName(content_meta_type), \ cnmt_ctx->packaged_header->title_id, \ cnmt_ctx->packaged_header->version.value, \ cnmt_ctx->packaged_header->version.application_version.release_ver, \ @@ -448,16 +450,16 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_ digest_str, \ cnmt_ctx->nca_ctx->key_generation)) goto end; - /* RequiredSystemVersion (Application, Patch) / RequiredApplicationVersion (AddOnContent). */ - /* PatchId (Application) / ApplicationId (Patch, AddOnContent). */ - if (cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Application || cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Patch || \ - cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_AddOnContent) + /* RequiredSystemVersion (Application, Patch) / RequiredApplicationVersion (AddOnContent, DataPatch). */ + /* PatchId (Application) / ApplicationId (Patch, AddOnContent, DataPatch). */ + if (content_meta_type == NcmContentMetaType_Application || content_meta_type == NcmContentMetaType_Patch || content_meta_type == NcmContentMetaType_AddOnContent || \ + content_meta_type == NcmContentMetaType_DataPatch) { u32 required_title_version = cnmtGetRequiredTitleVersion(cnmt_ctx); - const char *required_title_version_str = cnmtGetRequiredTitleVersionString(cnmt_ctx->packaged_header->content_meta_type); + const char *required_title_version_str = cnmtGetRequiredTitleVersionString(content_meta_type); u64 required_title_id = cnmtGetRequiredTitleId(cnmt_ctx); - const char *required_title_type_str = cnmtGetRequiredTitleTypeString(cnmt_ctx->packaged_header->content_meta_type); + const char *required_title_type_str = cnmtGetRequiredTitleTypeString(content_meta_type); if (!CNMT_ADD_FMT_STR(" <%s>%u\n" \ " <%s>0x%016lx\n", \ @@ -466,11 +468,18 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_ } /* RequiredApplicationVersion (Application). */ - if (cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Application) - { - if (!CNMT_ADD_FMT_STR(" %u\n", \ - ((ContentMetaApplicationMetaExtendedHeader*)cnmt_ctx->extended_header)->required_application_version.value)) goto end; - } + if (content_meta_type == NcmContentMetaType_Application && \ + !CNMT_ADD_FMT_STR(" %u\n", \ + ((ContentMetaApplicationMetaExtendedHeader*)cnmt_ctx->extended_header)->required_application_version.value)) goto end; + + /* DataPatchId (AddOnContent). */ + if (content_meta_type == NcmContentMetaType_AddOnContent && \ + cnmt_ctx->packaged_header->extended_header_size == (u16)sizeof(ContentMetaAddOnContentMetaExtendedHeader) && \ + !CNMT_ADD_FMT_STR(" 0x%016lx\n", ((ContentMetaAddOnContentMetaExtendedHeader*)cnmt_ctx->extended_header)->data_patch_id)) goto end; + + /* DataId (DataPatch). */ + if (content_meta_type == NcmContentMetaType_DataPatch && \ + !CNMT_ADD_FMT_STR(" 0x%016lx\n", ((ContentMetaDataPatchMetaExtendedHeader*)cnmt_ctx->extended_header)->data_id)) goto end; if (!(success = CNMT_ADD_FMT_STR(""))) goto end; @@ -509,7 +518,7 @@ static bool cnmtGetContentMetaTypeAndTitleIdFromFileName(const char *cnmt_filena return false; } - for(i = NcmContentMetaType_SystemProgram; i <= NcmContentMetaType_Delta; i++) + for(i = NcmContentMetaType_SystemProgram; i <= NcmContentMetaType_DataPatch; i++) { /* Dirty loop hack, but whatever. */ if (i > NcmContentMetaType_BootImagePackageSafe && i < NcmContentMetaType_Application) @@ -525,7 +534,7 @@ static bool cnmtGetContentMetaTypeAndTitleIdFromFileName(const char *cnmt_filena } } - if (i > NcmContentMetaType_Delta) + if (i > NcmContentMetaType_DataPatch) { LOG_MSG_ERROR("Invalid content meta type \"%.*s\" in '.cnmt' filename! (\"%s\").", (int)content_meta_type_str_len, cnmt_filename, cnmt_filename); return false; @@ -551,6 +560,7 @@ static const char *cnmtGetRequiredTitleVersionString(u8 content_meta_type) str = "RequiredSystemVersion"; break; case NcmContentMetaType_AddOnContent: + case NcmContentMetaType_DataPatch: str = "RequiredApplicationVersion"; break; default: diff --git a/source/core/keys.c b/source/core/keys.c index 577acb2..da2fa65 100644 --- a/source/core/keys.c +++ b/source/core/keys.c @@ -136,36 +136,36 @@ static const u8 g_ncaKaekBlockHashes[2][NcaKeyAreaEncryptionKeyIndex_Count][SHA2 { /* Application. */ { - 0x25, 0xDB, 0xC7, 0xB0, 0x55, 0x05, 0x46, 0xAF, 0xDA, 0xA0, 0xEE, 0xA8, 0x85, 0x3D, 0x7E, 0x3D, - 0x33, 0xD3, 0x5D, 0x86, 0x2C, 0xA7, 0x18, 0x2C, 0x83, 0xBB, 0x81, 0x79, 0xFC, 0x47, 0x91, 0x63 + 0xBD, 0x19, 0x22, 0x4B, 0xC4, 0x72, 0x0E, 0xAD, 0x9D, 0x5D, 0x99, 0x69, 0xEF, 0xF4, 0x91, 0x34, + 0x27, 0x73, 0xD6, 0x74, 0x62, 0xA3, 0xF9, 0x2D, 0x07, 0xB2, 0xAE, 0x6B, 0x19, 0xA9, 0xE2, 0x85 }, /* Ocean. */ { - 0x58, 0x00, 0x85, 0xA9, 0xE5, 0x2B, 0x3C, 0x50, 0xDB, 0x3A, 0x9F, 0xF2, 0x56, 0x61, 0xC2, 0x35, - 0x0C, 0xAB, 0xE8, 0xC2, 0x9B, 0x03, 0x0E, 0x2E, 0xDD, 0xF4, 0xC7, 0x5E, 0x7E, 0x1B, 0x7D, 0x06 + 0xC7, 0xC7, 0x5B, 0xB0, 0x9D, 0x4D, 0x46, 0xAA, 0xE8, 0xDB, 0xF6, 0x6D, 0x24, 0xEA, 0x41, 0x61, + 0x9F, 0x6D, 0x19, 0x2B, 0x3B, 0x79, 0x3F, 0x1B, 0x49, 0x60, 0x3D, 0xA9, 0x69, 0x84, 0xE5, 0x4D }, /* System. */ { - 0xB4, 0x11, 0x6E, 0x5D, 0xF6, 0x09, 0x72, 0x04, 0x0D, 0xCD, 0xEE, 0x8D, 0x74, 0x2D, 0x51, 0x1A, - 0xA1, 0x10, 0xA4, 0xFC, 0x0E, 0x2D, 0x6C, 0x0C, 0x85, 0x98, 0x62, 0x1F, 0x7A, 0x6F, 0x31, 0xD6 + 0xFE, 0x02, 0x86, 0x80, 0x8F, 0x88, 0x86, 0x3D, 0x64, 0x53, 0xFB, 0x64, 0xED, 0x2B, 0x51, 0xDA, + 0x5A, 0xE2, 0x22, 0x44, 0x00, 0x15, 0x33, 0xBA, 0xD1, 0xA4, 0xBE, 0xA2, 0xC0, 0x5E, 0x38, 0xF5 } }, /* Development. */ { /* Application. */ { - 0xD9, 0xBC, 0x7E, 0x09, 0xFD, 0x46, 0x43, 0xB7, 0x05, 0x5E, 0xAD, 0x60, 0x2A, 0xE4, 0x5B, 0xBC, - 0xA1, 0x6E, 0xB0, 0x93, 0x8C, 0x51, 0x0E, 0x93, 0x19, 0xE7, 0xD6, 0x00, 0x82, 0xEF, 0xCA, 0x85 + 0x6B, 0xD0, 0x5E, 0x57, 0x62, 0xD8, 0xD6, 0xBB, 0x00, 0xAD, 0xC0, 0xD7, 0x00, 0x94, 0x9F, 0xFF, + 0xF9, 0x03, 0x45, 0xA3, 0x07, 0x93, 0xCB, 0xF3, 0x7B, 0xF1, 0x9E, 0xC3, 0x4B, 0xA2, 0x52, 0xAE }, /* Ocean. */ { - 0x0F, 0xF6, 0x5E, 0xEC, 0xB9, 0x21, 0x7C, 0x66, 0x27, 0xBA, 0xBA, 0x18, 0xAF, 0x95, 0x3A, 0xEA, - 0x77, 0xA7, 0x43, 0x8F, 0xA3, 0x2B, 0x40, 0x85, 0xE8, 0x67, 0x4A, 0x28, 0xFF, 0xAE, 0x1D, 0xD5 + 0x56, 0x00, 0xAD, 0x5E, 0x8F, 0xEA, 0xD3, 0x24, 0x23, 0xDC, 0x81, 0xDB, 0x0F, 0xF9, 0xDF, 0x18, + 0xD8, 0x8E, 0xC4, 0xC9, 0x0B, 0x3F, 0x42, 0x64, 0xD2, 0xD4, 0x3D, 0xE0, 0x38, 0xFD, 0x53, 0xC1 }, /* System. */ { - 0x49, 0x63, 0x92, 0xE4, 0x97, 0x34, 0x9B, 0x78, 0x33, 0x73, 0x71, 0x84, 0xC4, 0x96, 0xBB, 0xE6, - 0x78, 0xD7, 0x4B, 0x31, 0xC1, 0x01, 0xA6, 0xB5, 0x8B, 0xC2, 0x26, 0x2D, 0xD0, 0x5E, 0xB5, 0xEE + 0x7B, 0x00, 0x0F, 0x31, 0x59, 0x36, 0x3A, 0x0E, 0xC5, 0x28, 0x4F, 0xE8, 0x73, 0x04, 0x4E, 0x7F, + 0xDC, 0x8C, 0xA4, 0x30, 0x88, 0xFF, 0x1F, 0xDB, 0x6B, 0x58, 0x71, 0xDA, 0xF8, 0xF0, 0x0B, 0xD6 } } }; @@ -174,13 +174,13 @@ static const u8 g_ncaKaekBlockHashes[2][NcaKeyAreaEncryptionKeyIndex_Count][SHA2 static const u8 g_ticketCommonKeysBlockHashes[2][SHA256_HASH_SIZE] = { /* Production. */ { - 0x26, 0xBC, 0x1F, 0x28, 0x06, 0x7E, 0x38, 0xF0, 0xBA, 0x3F, 0xF4, 0xAF, 0x3C, 0x2C, 0x5A, 0x11, - 0x62, 0x7E, 0x70, 0x30, 0x36, 0xED, 0xA9, 0xA7, 0xD7, 0xDB, 0x5F, 0x74, 0x1A, 0xB0, 0x7E, 0xB9 + 0xF3, 0x0D, 0x51, 0x85, 0x9F, 0x70, 0x66, 0x75, 0x79, 0x53, 0x6B, 0x2B, 0xFD, 0x29, 0x53, 0xEC, + 0x7A, 0x25, 0xF7, 0x41, 0x92, 0xE4, 0xC7, 0x21, 0x82, 0x73, 0x46, 0x74, 0x82, 0xB3, 0x48, 0x07 }, /* Development. */ { - 0xF5, 0x77, 0x10, 0x17, 0x13, 0x4B, 0x4E, 0xD4, 0xBF, 0x24, 0x0B, 0xF4, 0xBB, 0x6E, 0x4D, 0x24, - 0x6C, 0xC3, 0x0C, 0x60, 0x93, 0x96, 0x9F, 0xD5, 0xA9, 0xA9, 0xB4, 0xD5, 0xD5, 0x44, 0xA6, 0x39 + 0x0A, 0x94, 0x77, 0x9F, 0xE2, 0x86, 0x33, 0xF4, 0x91, 0x84, 0xE9, 0x88, 0x56, 0xAA, 0xA4, 0x6C, + 0x12, 0x55, 0x62, 0x64, 0x21, 0x2E, 0xAD, 0x41, 0x36, 0x22, 0xDC, 0x3A, 0xA7, 0x22, 0xFC, 0x3C } }; @@ -965,7 +965,7 @@ static bool keysReadKeysFromFile(void) } #define PARSE_HEX_KEY(name, out, decl) \ - if (!strcmp(key, name) && keysParseHexKey(out, key, value, sizeof(out))) { \ + if (!strcasecmp(key, name) && keysParseHexKey(out, key, value, sizeof(out))) { \ key_count++; \ decl; \ } @@ -1061,6 +1061,7 @@ static bool keysGetDecryptedEticketRsaDeviceKey(void) const u8 *eticket_rsa_kek = NULL; EticketRsaDeviceKey *eticket_rsa_key = NULL; Aes128CtrContext eticket_aes_ctx = {0}; + const u8 nullkey[AES_128_KEY_SIZE] = {0}; /* Get eTicket RSA device key. */ rc = setcalGetEticketDeviceKey(&g_eTicketRsaDeviceKey); @@ -1073,6 +1074,12 @@ static bool keysGetDecryptedEticketRsaDeviceKey(void) /* Get eTicket RSA device key encryption key. */ eticket_rsa_kek = (const u8*)(g_eTicketRsaDeviceKey.generation > 0 ? g_ncaKeyset.eticket_rsa_kek_personalized : g_ncaKeyset.eticket_rsa_kek); + if (!memcmp(eticket_rsa_kek, nullkey, sizeof(nullkey))) + { + LOG_MSG_ERROR("Empty \"eticket_rsa_kek\" key entry! (0x%X).", g_eTicketRsaDeviceKey.generation); + return false; + } + /* Decrypt eTicket RSA device key. */ eticket_rsa_key = (EticketRsaDeviceKey*)g_eTicketRsaDeviceKey.key; aes128CtrContextCreate(&eticket_aes_ctx, eticket_rsa_kek, eticket_rsa_key->ctr); diff --git a/source/core/nca.c b/source/core/nca.c index dce04a1..07d0256 100644 --- a/source/core/nca.c +++ b/source/core/nca.c @@ -61,7 +61,7 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx); static bool ncaFsSectionValidateHashDataBoundaries(NcaFsSectionContext *ctx); static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset); -static bool ncaFsSectionCheckHashRegionAccess(NcaFsSectionContext *ctx, u64 offset, u64 size, u64 *out_chunk_size); +static bool ncaFsSectionCheckPlaintextHashRegionAccess(NcaFsSectionContext *ctx, u64 offset, u64 size, NcaRegion *out_region); static bool _ncaReadAesCtrExStorage(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val, bool decrypt); @@ -122,8 +122,8 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, out->content_type = content_info->content_type; out->id_offset = content_info->id_offset; out->title_version = title_version; + ncmContentInfoSizeToU64(content_info, &(out->content_size)); - titleConvertNcmContentSizeToU64(content_info->size, &(out->content_size)); if (out->content_size < NCA_FULL_HEADER_LENGTH) { LOG_MSG_ERROR("Invalid size for NCA \"%s\"!", out->content_id_str); @@ -469,10 +469,10 @@ const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx) switch(ctx->section_type) { case NcaFsSectionType_PartitionFs: - str = (is_exefs ? "ExeFS" : "Partition FS"); + str = (is_exefs ? (ctx->has_sparse_layer ? "ExeFS (sparse)" : "ExeFS") : (ctx->has_sparse_layer ? "PartitionFS (sparse)" : "PartitionFS")); break; case NcaFsSectionType_RomFs: - str = "RomFS"; + str = (ctx->has_sparse_layer ? "RomFS (sparse)" : "RomFS"); break; case NcaFsSectionType_PatchRomFs: str = "Patch RomFS"; @@ -783,7 +783,7 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx) if (fs_ctx->section_offset < sizeof(NcaHeader) || !fs_ctx->section_size) { LOG_MSG_ERROR("Invalid offset/size for FS section #%u in \"%s\" (0x%lX, 0x%lX). Skipping FS section.", section_idx, nca_ctx->content_id_str, fs_ctx->section_offset, \ - fs_ctx->section_size); + fs_ctx->section_size); goto end; } @@ -918,15 +918,6 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx) goto end; } - /* Check if we're dealing with a bogus Patch RomFS (seem to be available in HtmlDocument NCAs). */ - if (fs_ctx->section_type == NcaFsSectionType_PatchRomFs && fs_ctx->section_size <= (fs_ctx->header.patch_info.indirect_bucket.size + fs_ctx->header.patch_info.aes_ctr_ex_bucket.size)) - { - /* Return true but don't set this FS section as enabled, since we can't really use it. */ - LOG_MSG_WARNING("Empty Patch RomFS data detected for FS section #%u in \"%s\". Skipping FS section.", section_idx, nca_ctx->content_id_str); - success = true; - goto end; - } - /* Validate HashData boundaries. */ if (!ncaFsSectionValidateHashDataBoundaries(fs_ctx)) goto end; @@ -938,8 +929,10 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx) goto end; } - /* Check if we're within boundaries. */ - if (fs_ctx->hash_region.size > fs_ctx->section_size || (fs_ctx->section_offset + fs_ctx->hash_region.size) > nca_ctx->content_size) + /* Check if we're within physical boundaries, but only if we're not dealing with a Patch RomFS or a sparse layer. */ + /* The hash layers before the target layer may exceed the section size. */ + if (fs_ctx->section_type != NcaFsSectionType_PatchRomFs && !fs_ctx->has_sparse_layer && (fs_ctx->hash_region.size > fs_ctx->section_size || \ + (fs_ctx->section_offset + fs_ctx->hash_region.size) > nca_ctx->content_size)) { LOG_MSG_ERROR("Hash layer region for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", section_idx, nca_ctx->content_id_str); goto end; @@ -956,28 +949,18 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx) /* Check if we're dealing with a compression layer. */ if (fs_ctx->has_compression_layer) { - u64 raw_storage_offset = 0; - u64 raw_storage_size = compression_bucket->size; + u64 bucket_offset = 0; + u64 bucket_size = compression_bucket->size; - if (fs_ctx->section_type != NcaFsSectionType_PatchRomFs) + /* Calculate section-relative compression bucket offset, but only if we're not dealing with a Patch RomFS or a section with a sparse layer. */ + if (fs_ctx->section_type != NcaFsSectionType_PatchRomFs && !fs_ctx->has_sparse_layer) bucket_offset = (fs_ctx->hash_region.size + compression_bucket->offset); + + /* Check if the compression bucket is valid. Don't verify extents if we're dealing with a Patch RomFS or a section with a sparse layer. */ + if (!ncaVerifyBucketInfo(compression_bucket) || !compression_bucket->header.entry_count || (bucket_offset && (bucket_offset < sizeof(NcaHeader) || \ + (bucket_offset + bucket_size) > fs_ctx->section_size || (fs_ctx->section_offset + bucket_offset + bucket_size) > nca_ctx->content_size))) { - /* Get target hash layer offset. */ - if (!ncaGetFsSectionHashTargetExtents(fs_ctx, &raw_storage_offset, NULL)) - { - LOG_MSG_ERROR("Invalid hash type for FS section #%u in \"%s\" (0x%02X). Skipping FS section.", fs_ctx->section_idx, nca_ctx->content_id_str, fs_ctx->hash_type); - goto end; - } - - /* Update compression layer offset. */ - raw_storage_offset += compression_bucket->offset; - } - - /* Check if the compression bucket is valid. Don't verify extents if we're dealing with a Patch RomFS. */ - if (!ncaVerifyBucketInfo(compression_bucket) || !compression_bucket->header.entry_count || (raw_storage_offset && (raw_storage_offset < sizeof(NcaHeader) || \ - (raw_storage_offset + raw_storage_size) > fs_ctx->section_size || (fs_ctx->section_offset + raw_storage_offset + raw_storage_size) > nca_ctx->content_size))) - { - LOG_DATA_ERROR(compression_bucket, sizeof(NcaBucketInfo), "Invalid CompressionInfo data for FS section #%u in \"%s\" (0x%lX). Skipping FS section. CompressionInfo dump:", \ - section_idx, nca_ctx->content_id_str, nca_ctx->content_size); + LOG_DATA_ERROR(compression_bucket, sizeof(NcaBucketInfo), "Invalid CompressionInfo data for FS section #%u in \"%s\" (0x%lX, 0x%lX, 0x%lX). Skipping FS section. CompressionInfo dump:", \ + section_idx, nca_ctx->content_id_str, bucket_offset, fs_ctx->section_size, nca_ctx->content_size); goto end; } } @@ -1018,6 +1001,10 @@ end: static bool ncaFsSectionValidateHashDataBoundaries(NcaFsSectionContext *ctx) { + /* Return right away if we're dealing with a Patch RomFS or if a sparse layer is used. */ + /* We can't validate what we don't fully have access to. */ + if (ctx->section_type == NcaFsSectionType_PatchRomFs || ctx->has_sparse_layer) return true; + #if LOG_LEVEL <= LOG_LEVEL_WARNING const char *content_id_str = ctx->nca_ctx->content_id_str; #endif @@ -1033,67 +1020,62 @@ static bool ncaFsSectionValidateHashDataBoundaries(NcaFsSectionContext *ctx) break; case NcaHashType_HierarchicalSha256: case NcaHashType_HierarchicalSha3256: + { + NcaHierarchicalSha256Data *hash_data = &(ctx->header.hash_data.hierarchical_sha256_data); + if (!hash_data->hash_block_size || !hash_data->hash_region_count || hash_data->hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT) { - NcaHierarchicalSha256Data *hash_data = &(ctx->header.hash_data.hierarchical_sha256_data); - if (!hash_data->hash_block_size || !hash_data->hash_region_count || hash_data->hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT) + LOG_DATA_WARNING(hash_data, sizeof(NcaHierarchicalSha256Data), "Invalid HierarchicalSha256 data for FS section #%u in \"%s\". Skipping FS section. Hash data dump:", \ + ctx->section_idx, content_id_str); + break; + } + + for(u32 i = 0; i < hash_data->hash_region_count; i++) + { + /* Validate all hash regions boundaries. */ + NcaRegion *hash_region = &(hash_data->hash_region[i]); + if (hash_region->offset < accum || !hash_region->size || (i < (hash_data->hash_region_count - 1) && (hash_region->offset + hash_region->size) > ctx->section_size)) { - LOG_DATA_WARNING(hash_data, sizeof(NcaHierarchicalSha256Data), "Invalid HierarchicalSha256 data for FS section #%u in \"%s\". Skipping FS section. Hash data dump:", \ - ctx->section_idx, content_id_str); + LOG_DATA_WARNING(hash_data, sizeof(NcaHierarchicalSha256Data), "HierarchicalSha256 region #%u for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section. Hash data dump:", \ + i, ctx->section_idx, content_id_str); + valid = false; break; } - for(u32 i = 0; i < hash_data->hash_region_count; i++) - { - /* Validate all hash regions boundaries. Skip the last one if a sparse layer is used. */ - NcaRegion *hash_region = &(hash_data->hash_region[i]); - if (hash_region->offset < accum || !hash_region->size || \ - ((i < (hash_data->hash_region_count - 1) || !ctx->has_sparse_layer) && (hash_region->offset + hash_region->size) > ctx->section_size)) - { - LOG_MSG_WARNING("HierarchicalSha256 region #%u for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", \ - i, ctx->section_idx, content_id_str); - valid = false; - break; - } - - accum = (hash_region->offset + hash_region->size); - } - - success = valid; + accum = (hash_region->offset + hash_region->size); } + success = valid; break; + } case NcaHashType_HierarchicalIntegrity: case NcaHashType_HierarchicalIntegritySha3: + { + NcaIntegrityMetaInfo *hash_data = &(ctx->header.hash_data.integrity_meta_info); + if (__builtin_bswap32(hash_data->magic) != NCA_IVFC_MAGIC || hash_data->master_hash_size != SHA256_HASH_SIZE || hash_data->info_level_hash.max_level_count != NCA_IVFC_MAX_LEVEL_COUNT) { - NcaIntegrityMetaInfo *hash_data = &(ctx->header.hash_data.integrity_meta_info); - if (__builtin_bswap32(hash_data->magic) != NCA_IVFC_MAGIC || hash_data->master_hash_size != SHA256_HASH_SIZE || \ - hash_data->info_level_hash.max_level_count != NCA_IVFC_MAX_LEVEL_COUNT) + LOG_DATA_WARNING(hash_data, sizeof(NcaIntegrityMetaInfo), "Invalid HierarchicalIntegrity data for FS section #%u in \"%s\". Skipping FS section. Hash data dump:", \ + ctx->section_idx, content_id_str); + break; + } + + for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++) + { + /* Validate all level informations boundaries. */ + NcaHierarchicalIntegrityVerificationLevelInformation *lvl_info = &(hash_data->info_level_hash.level_information[i]); + if (lvl_info->offset < accum || !lvl_info->size || !lvl_info->block_order || (i < (NCA_IVFC_LEVEL_COUNT - 1) && (lvl_info->offset + lvl_info->size) > ctx->section_size)) { - LOG_DATA_WARNING(hash_data, sizeof(NcaIntegrityMetaInfo), "Invalid HierarchicalIntegrity data for FS section #%u in \"%s\". Skipping FS section. Hash data dump:", \ - ctx->section_idx, content_id_str); + LOG_DATA_WARNING(hash_data, sizeof(NcaIntegrityMetaInfo), "HierarchicalIntegrity level #%u for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section. Hash data dump:", \ + i, ctx->section_idx, content_id_str); + valid = false; break; } - for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++) - { - /* Validate all level informations boundaries. Skip the last one if we're dealing with a Patch RomFS, or if a sparse layer is used. */ - NcaHierarchicalIntegrityVerificationLevelInformation *lvl_info = &(hash_data->info_level_hash.level_information[i]); - if (lvl_info->offset < accum || !lvl_info->size || !lvl_info->block_order || ((i < (NCA_IVFC_LEVEL_COUNT - 1) || \ - (!ctx->has_sparse_layer && ctx->section_type != NcaFsSectionType_PatchRomFs)) && (lvl_info->offset + lvl_info->size) > ctx->section_size)) - { - LOG_MSG_WARNING("HierarchicalIntegrity level #%u for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", \ - i, ctx->section_idx, content_id_str); - valid = false; - break; - } - - accum = (lvl_info->offset + lvl_info->size); - } - - success = valid; + accum = (lvl_info->offset + lvl_info->size); } + success = valid; break; + } default: LOG_MSG_WARNING("Invalid hash type for FS section #%u in \"%s\" (0x%02X). Skipping FS section.", ctx->section_idx, content_id_str, ctx->hash_type); break; @@ -1124,6 +1106,8 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size u64 block_start_offset = 0, block_end_offset = 0, block_size = 0; u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 0; + NcaRegion plaintext_area = {0}; + bool ret = false; if (!*(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || \ @@ -1135,35 +1119,40 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size goto end; } - /* Check if we're supposed to read a hash layer without encryption. */ - if (ncaFsSectionCheckHashRegionAccess(ctx, offset, read_size, &block_size)) + /* Check if we're about to read a plaintext hash layer. */ + if (ncaFsSectionCheckPlaintextHashRegionAccess(ctx, offset, read_size, &plaintext_area)) { - /* Read plaintext area. Use NCA-relative offset. */ - if (!ncaReadContentFile(nca_ctx, out, block_size, content_offset)) + bool plaintext_first = (plaintext_area.offset == offset); + + /* Read first chunk. */ + /* It may be plaintext or not depending on the returned hash region properties. */ + block_size = (plaintext_first ? plaintext_area.size : (plaintext_area.offset - offset)); + + if ((plaintext_first && !ncaReadContentFile(nca_ctx, out, block_size, content_offset)) || (!plaintext_first && !_ncaReadFsSection(ctx, out, block_size, offset))) { LOG_MSG_ERROR("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (plaintext hash region) (#1).", block_size, content_offset, \ - nca_ctx->content_id_str, ctx->section_idx); + nca_ctx->content_id_str, ctx->section_idx); goto end; } - /* Read remaining encrypted data, if needed. Use FS-section-relative offset. */ - if (sparse_virtual_offset) ctx->cur_sparse_virtual_offset += block_size; - ret = (read_size ? _ncaReadFsSection(ctx, (u8*)out + block_size, read_size - block_size, offset + block_size) : true); - goto end; - } else - if (block_size && block_size < read_size) - { - /* Read encrypted area. Use FS-section-relative offset. */ - if (!_ncaReadFsSection(ctx, out, block_size, offset)) goto end; - /* Update parameters. */ read_size -= block_size; + offset += block_size; content_offset += block_size; + if (sparse_virtual_offset) ctx->cur_sparse_virtual_offset += block_size; - /* Read remaining plaintext data. Use NCA-relative offset. */ - ret = ncaReadContentFile(nca_ctx, (u8*)out + block_size, read_size, content_offset); - if (!ret) LOG_MSG_ERROR("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (plaintext hash region) (#2).", read_size, content_offset, \ + /* Read second chunk. */ + /* It may be plaintext or not depending on the returned hash region properties. */ + if (read_size && ((plaintext_first && !_ncaReadFsSection(ctx, (u8*)out + block_size, read_size, offset)) || \ + (!plaintext_first && !ncaReadContentFile(nca_ctx, (u8*)out + block_size, read_size, content_offset)))) + { + LOG_MSG_ERROR("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (plaintext hash region) (#2).", read_size, content_offset, \ nca_ctx->content_id_str, ctx->section_idx); + goto end; + } + + ret = true; + goto end; } @@ -1195,7 +1184,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size if (crypt_res != read_size) { LOG_MSG_ERROR("Failed to AES-XTS decrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned).", read_size, content_offset, nca_ctx->content_id_str, \ - ctx->section_idx); + ctx->section_idx); goto end; } } else @@ -1223,7 +1212,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size if (!ncaReadContentFile(nca_ctx, g_ncaCryptoBuffer, chunk_size, block_start_offset)) { LOG_MSG_ERROR("Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned).", chunk_size, block_start_offset, nca_ctx->content_id_str, \ - ctx->section_idx); + ctx->section_idx); goto end; } @@ -1236,7 +1225,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size if (crypt_res != chunk_size) { LOG_MSG_ERROR("Failed to AES-XTS decrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned).", chunk_size, block_start_offset, nca_ctx->content_id_str, \ - ctx->section_idx); + ctx->section_idx); goto end; } } else @@ -1260,33 +1249,34 @@ end: return ret; } -static bool ncaFsSectionCheckHashRegionAccess(NcaFsSectionContext *ctx, u64 offset, u64 size, u64 *out_chunk_size) +static bool ncaFsSectionCheckPlaintextHashRegionAccess(NcaFsSectionContext *ctx, u64 offset, u64 size, NcaRegion *out_region) { if (!ctx->skip_hash_layer_crypto) return false; NcaRegion *hash_region = &(ctx->hash_region); + bool ret = false; + + memset(out_region, 0, sizeof(NcaRegion)); /* Check if our region contains the access. */ if (hash_region->offset <= offset) { if (offset < (hash_region->offset + hash_region->size)) { - if ((hash_region->offset + hash_region->size) <= (offset + size)) - { - *out_chunk_size = ((hash_region->offset + hash_region->size) - offset); - } else { - *out_chunk_size = size; - } - - return true; - } else { - return false; + out_region->offset = offset; + out_region->size = ((hash_region->offset + hash_region->size) <= (offset + size) ? ((hash_region->offset + hash_region->size) - offset) : size); + ret = true; } } else { - if (hash_region->offset <= (offset + size)) *out_chunk_size = (hash_region->offset - offset); - - return false; + if (hash_region->offset < (offset + size)) + { + out_region->offset = hash_region->offset; + out_region->size = ((offset + size) <= (hash_region->offset + hash_region->size) ? ((offset + size) - hash_region->offset) : hash_region->size); + ret = true; + } } + + return ret; } static bool _ncaReadAesCtrExStorage(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val, bool decrypt) @@ -1349,7 +1339,7 @@ static bool _ncaReadAesCtrExStorage(NcaFsSectionContext *ctx, void *out, u64 rea if (!ncaReadContentFile(nca_ctx, g_ncaCryptoBuffer, chunk_size, block_start_offset)) { LOG_MSG_ERROR("Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned).", chunk_size, block_start_offset, nca_ctx->content_id_str, \ - ctx->section_idx); + ctx->section_idx); goto end; } diff --git a/source/core/nca_storage.c b/source/core/nca_storage.c index 438bc1d..7464c3e 100644 --- a/source/core/nca_storage.c +++ b/source/core/nca_storage.c @@ -75,8 +75,8 @@ bool ncaStorageInitializeContext(NcaStorageContext *out, NcaFsSectionContext *nc out->base_storage_type = NcaStorageBaseStorageType_Indirect; } - /* Initialize compression layer if it's available. */ - if (nca_fs_ctx->has_compression_layer && !ncaStorageInitializeCompressedStorageBucketTreeContext(out, nca_fs_ctx)) goto end; + /* Initialize compression layer if it's available, but only if we're also not dealing with a sparse layer. */ + if (nca_fs_ctx->has_compression_layer && !nca_fs_ctx->has_sparse_layer && !ncaStorageInitializeCompressedStorageBucketTreeContext(out, nca_fs_ctx)) goto end; /* Update output context. */ out->nca_fs_ctx = nca_fs_ctx; @@ -100,7 +100,8 @@ bool ncaStorageSetPatchOriginalSubStorage(NcaStorageContext *patch_ctx, NcaStora patch_nca_ctx->header.program_id != base_nca_ctx->header.program_id || patch_nca_ctx->header.content_type != base_nca_ctx->header.content_type || \ patch_nca_ctx->id_offset != base_nca_ctx->id_offset || patch_nca_ctx->title_version < base_nca_ctx->title_version || \ (patch_ctx->base_storage_type != NcaStorageBaseStorageType_Indirect && patch_ctx->base_storage_type != NcaStorageBaseStorageType_Compressed) || \ - !patch_ctx->indirect_storage || !patch_ctx->aes_ctr_ex_storage) + !patch_ctx->indirect_storage || !patch_ctx->aes_ctr_ex_storage || (base_ctx->base_storage_type == NcaStorageBaseStorageType_Compressed && \ + patch_ctx->base_storage_type != NcaStorageBaseStorageType_Compressed)) { LOG_MSG_ERROR("Invalid parameters!"); return false; @@ -112,30 +113,28 @@ bool ncaStorageSetPatchOriginalSubStorage(NcaStorageContext *patch_ctx, NcaStora switch(base_ctx->base_storage_type) { case NcaStorageBaseStorageType_Regular: + case NcaStorageBaseStorageType_Compressed: + /* Regular: we just make the Patch's Indirect Storage's SubStorage #0 point to the Base NCA FS section as-is and call it a day. */ + + /* Compressed: if a Compressed Storage is available in the Base NCA FS section, the corresponding Patch NCA FS section *must* also have one. */ + /* This is because the Patch's Compressed Storage also takes care of LZ4-compressed chunks within Base NCA FS section areas. */ + /* Furthermore, the Patch's Indirect Storage already provides section-relative physical offsets for the Base NCA FS section. */ + /* In other words, we don't need to parse the Base NCA's Compressed Storage on every read. */ success = bktrSetRegularSubStorage(patch_ctx->indirect_storage, base_ctx->nca_fs_ctx); break; case NcaStorageBaseStorageType_Sparse: + /* Sparse: we should *always* arrive here if a Sparse Storage is available in the Base NCA FS section, regardless if a Compressed Storage is available or not. */ + /* This is because compression bucket trees are non-existent in Base NCA FS sections that have both Sparse and Compressed Storages. */ + /* Furthermore, in these cases, the compression BucketInfo from the NCA FS section header references the full, patched FS section, so we can't really use it. */ + /* We just completely ignore the Base's Compressed Storage and let the Patch's Compressed Storage take care of LZ4-compressed chunks. */ + /* Anyway, we just make the Patch's Indirect Storage's SubStorage #0 point to the Base's Sparse Storage and call it a day. */ success = bktrSetBucketTreeSubStorage(patch_ctx->indirect_storage, base_ctx->sparse_storage, 0); - break; - case NcaStorageBaseStorageType_Compressed: - if (patch_ctx->base_storage_type == NcaStorageBaseStorageType_Compressed) - { - /* If Compressed Storages are available in both base and patch NCAs, the Patch's Indirect storage already provides section-relative physical offsets. */ - /* We don't need to parse the base NCA's Compressed Storage on every read. */ - success = bktrSetRegularSubStorage(patch_ctx->indirect_storage, base_ctx->nca_fs_ctx); - } else { - /* No Compressed Storage available in the patch NCA. */ - /* We'll need to parse the base NCA's Compressed Storage on every read. */ - /* TODO: check if this combination is even possible. */ - success = bktrSetBucketTreeSubStorage(patch_ctx->indirect_storage, base_ctx->compressed_storage, 0); - } - break; default: break; } - if (!success) LOG_MSG_ERROR("Failed to set base storage to patch storage!"); + if (!success) LOG_MSG_ERROR("Failed to set base storage to patch storage! (0x%02X, 0x%02X).", base_ctx->base_storage_type, patch_ctx->base_storage_type); return success; } @@ -148,16 +147,8 @@ bool ncaStorageGetHashTargetExtents(NcaStorageContext *ctx, u64 *out_offset, u64 return false; } - u64 hash_target_offset = 0, hash_target_size = 0; bool success = false; - /* Get hash target extents from the NCA FS section. */ - if (!ncaGetFsSectionHashTargetExtents(ctx->nca_fs_ctx, &hash_target_offset, &hash_target_size)) - { - LOG_MSG_ERROR("Failed to retrieve NCA FS section's hash target extents!"); - goto end; - } - /* Set proper hash target extents. */ switch(ctx->base_storage_type) { @@ -165,6 +156,15 @@ bool ncaStorageGetHashTargetExtents(NcaStorageContext *ctx, u64 *out_offset, u64 case NcaStorageBaseStorageType_Sparse: case NcaStorageBaseStorageType_Indirect: { + u64 hash_target_offset = 0, hash_target_size = 0; + + /* Get hash target extents from the NCA FS section. */ + if (!ncaGetFsSectionHashTargetExtents(ctx->nca_fs_ctx, &hash_target_offset, &hash_target_size)) + { + LOG_MSG_ERROR("Failed to retrieve NCA FS section's hash target extents!"); + goto end; + } + /* Regular: just provide the NCA FS section hash target extents -- they already represent physical information. */ /* Sparse/Indirect: the base storage's virtual section encompasses the hash layers, too. The NCA FS section hash target extents represent valid virtual information. */ if (out_offset) *out_offset = hash_target_offset; diff --git a/source/core/title.c b/source/core/title.c index a670a80..4083e9b 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -92,14 +92,16 @@ static const char *g_titleNcmContentMetaTypeNames[] = { [NcmContentMetaType_Application - 0x7A] = "Application", [NcmContentMetaType_Patch - 0x7A] = "Patch", [NcmContentMetaType_AddOnContent - 0x7A] = "AddOnContent", - [NcmContentMetaType_Delta - 0x7A] = "Delta" + [NcmContentMetaType_Delta - 0x7A] = "Delta", + [NcmContentMetaType_DataPatch - 0x7A] = "DataPatch" }; static const char *g_filenameTypeStrings[] = { [NcmContentMetaType_Application - 0x80] = "BASE", [NcmContentMetaType_Patch - 0x80] = "UPD", [NcmContentMetaType_AddOnContent - 0x80] = "DLC", - [NcmContentMetaType_Delta - 0x80] = "DELTA" + [NcmContentMetaType_Delta - 0x80] = "DELTA", + [NcmContentMetaType_DataPatch - 0x80] = "DLCUPD" }; /* Info retrieved from https://switchbrew.org/wiki/Title_list. */ @@ -175,6 +177,7 @@ static const TitleSystemEntry g_systemTitles[] = { { 0x0100000000000041, "ngct" }, { 0x0100000000000042, "pgl" }, { 0x0100000000000045, "omm" }, + { 0x0100000000000046, "eth" }, /* System data archives. */ /* Meta + Data NCAs. */ @@ -525,7 +528,7 @@ static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *pare 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); +static int titleInfoEntrySortFunction(const void *a, const void *b); static char *titleGetPatchVersionString(TitleInfo *title_info); @@ -987,8 +990,9 @@ bool titleIsGameCardInfoUpdated(void) char *titleGenerateFileName(TitleInfo *title_info, u8 naming_convention, u8 illegal_char_replace_type) { - if (!title_info || title_info->meta_key.type < NcmContentMetaType_Application || title_info->meta_key.type > NcmContentMetaType_Delta || naming_convention > TitleNamingConvention_IdAndVersionOnly || \ - (naming_convention == TitleNamingConvention_Full && illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly)) + if (!title_info || title_info->meta_key.type < NcmContentMetaType_Application || title_info->meta_key.type > NcmContentMetaType_DataPatch || \ + naming_convention > TitleNamingConvention_IdAndVersionOnly || (naming_convention == TitleNamingConvention_Full && \ + illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly)) { LOG_MSG_ERROR("Invalid parameters!"); return NULL; @@ -1164,7 +1168,7 @@ const char *titleGetNcmContentTypeName(u8 content_type) const char *titleGetNcmContentMetaTypeName(u8 content_meta_type) { - if ((content_meta_type > NcmContentMetaType_BootImagePackageSafe && content_meta_type < NcmContentMetaType_Application) || content_meta_type > NcmContentMetaType_Delta) return NULL; + if ((content_meta_type > NcmContentMetaType_BootImagePackageSafe && content_meta_type < NcmContentMetaType_Application) || content_meta_type > NcmContentMetaType_DataPatch) return NULL; return (content_meta_type <= NcmContentMetaType_BootImagePackageSafe ? g_titleNcmContentMetaTypeNames[content_meta_type] : g_titleNcmContentMetaTypeNames[content_meta_type - 0x7A]); } @@ -1475,8 +1479,8 @@ static void titleAddOrphanTitleInfoEntry(TitleInfo *orphan_title) /* Set orphan title info entry pointer. */ g_orphanTitleInfo[g_orphanTitleInfoCount++] = orphan_title; - /* Sort orphan title info entries by title ID. */ - if (g_orphanTitleInfoCount > 1) qsort(g_orphanTitleInfo, g_orphanTitleInfoCount, sizeof(TitleInfo*), &titleOrphanTitleInfoSortFunction); + /* Sort orphan title info entries by title ID, version and storage ID. */ + if (g_orphanTitleInfoCount > 1) qsort(g_orphanTitleInfo, g_orphanTitleInfoCount, sizeof(TitleInfo*), &titleInfoEntrySortFunction); } static bool titleGenerateMetadataEntriesFromSystemTitles(void) @@ -1747,7 +1751,7 @@ static bool titleRetrieveUserApplicationMetadataByTitleId(u64 title_id, TitleApp NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id, bool is_system, u32 extra_app_count) { - if ((is_system && (!g_systemMetadata || !g_systemMetadataCount)) || (!is_system && (!g_userMetadata || !g_userMetadataCount)) || !title_id) return NULL; + if (!title_id || (is_system && (!g_systemMetadata || !g_systemMetadataCount)) || (!is_system && (!g_userMetadata || !g_userMetadataCount))) return NULL; TitleApplicationMetadata **cached_app_metadata = (is_system ? g_systemMetadata : g_userMetadata); u32 cached_app_metadata_count = ((is_system ? g_systemMetadataCount : g_userMetadataCount) + extra_app_count); @@ -1823,7 +1827,7 @@ static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_sto /* Calculate title size. */ for(u32 j = 0; j < cur_title_info->content_count; j++) { - titleConvertNcmContentSizeToU64(cur_title_info->content_infos[j].size, &tmp_size); + ncmContentInfoSizeToU64(&(cur_title_info->content_infos[j]), &tmp_size); cur_title_info->size += tmp_size; } @@ -1837,7 +1841,8 @@ static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_sto 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->meta_key.type == NcmContentMetaType_Delta ? titleGetApplicationIdByDeltaId(cur_title_info->meta_key.id) : \ + (cur_title_info->meta_key.type == NcmContentMetaType_DataPatch ? titleGetApplicationIdByDataPatchId(cur_title_info->meta_key.id) : 0))))); cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, storage_id == NcmStorageId_BuiltInSystem, 0); if (!cur_title_info->app_metadata && storage_id == NcmStorageId_BuiltInSystem) @@ -1863,6 +1868,9 @@ static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_sto /* Free extra allocated pointers if we didn't use them. */ if (extra_title_count < total) titleReallocateTitleInfoFromStorage(title_storage, 0, false); + /* Sort title info entries by title ID, version and storage ID. */ + qsort(title_storage->titles, title_storage->title_count, sizeof(TitleInfo*), &titleInfoEntrySortFunction); + /* 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(); @@ -2060,21 +2068,42 @@ static void titleUpdateTitleInfoLinkedLists(void) for(u32 j = 0; j < title_count; 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; + if (!child_info) 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) + /* If we're dealing with a title that's not an user application, patch, add-on content or add-on content patch, flag it as orphan and proceed onto the next one. */ + if (child_info->meta_key.type < NcmContentMetaType_Application || (child_info->meta_key.type > NcmContentMetaType_AddOnContent && \ + child_info->meta_key.type != NcmContentMetaType_DataPatch)) { - /* 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. */ + titleAddOrphanTitleInfoEntry(child_info); + continue; + } + + if (child_info->meta_key.type != NcmContentMetaType_Application) + { + /* We're dealing with a patch, an add-on content or an add-on content patch. */ + /* Patch, AddOnContent: retrieve pointer to the first parent user application entry and set it as the parent title. */ + /* DataPatch: retrieve pointer to the first parent add-on content entry and set it as the parent title. */ u64 app_id = (child_info->meta_key.type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(child_info->meta_key.id) : \ - titleGetApplicationIdByAddOnContentId(child_info->meta_key.id)); + (child_info->meta_key.type == NcmContentMetaType_AddOnContent ? titleGetApplicationIdByAddOnContentId(child_info->meta_key.id) : \ + titleGetAddOnContentIdByDataPatchId(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; + + /* Set pointer to application metadata. */ + if (child_info->parent && !child_info->app_metadata) + { + if (child_info->meta_key.type != NcmContentMetaType_DataPatch || child_info->parent->app_metadata) + { + child_info->app_metadata = child_info->parent->app_metadata; + } else { + /* We may be dealing with a parent add-on content with a yet-to-be-assigned application metadata pointer. */ + TitleInfo *tmp_title_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, titleGetApplicationIdByDataPatchId(child_info->meta_key.id)); + if (tmp_title_info) child_info->parent->app_metadata = child_info->app_metadata = tmp_title_info->app_metadata; + } + } /* Add orphan title info entry if we have no application metadata. */ if (!child_info->app_metadata) @@ -2084,7 +2113,7 @@ static void titleUpdateTitleInfoLinkedLists(void) } } - /* Locate previous user application, patch or add-on content entry. */ + /* Locate previous user application, patch, add-on content or add-on content patch 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--) { @@ -2105,7 +2134,7 @@ static void titleUpdateTitleInfoLinkedLists(void) 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 && 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; @@ -2236,10 +2265,11 @@ static bool titleRefreshGameCardTitleInfo(void) 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->meta_key.type == NcmContentMetaType_Delta ? titleGetApplicationIdByDeltaId(cur_title_info->meta_key.id) : \ + (cur_title_info->meta_key.type == NcmContentMetaType_DataPatch ? titleGetApplicationIdByDataPatchId(cur_title_info->meta_key.id) : 0))))); - /* 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, false, extra_app_count)) != NULL) continue; + /* Do not proceed if we couldn't retrieve an application ID, if application metadata has already been retrieved, or if we can successfully retrieve it. */ + if (!app_id || cur_title_info->app_metadata != NULL || (cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, false, extra_app_count)) != NULL) continue; /* Retrieve application metadata pointer. */ TitleApplicationMetadata *cur_app_metadata = g_userMetadata[g_userMetadataCount + extra_app_count]; @@ -2330,7 +2360,8 @@ static bool titleIsUserApplicationContentAvailable(u64 app_id) 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; + (title_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, title_info->meta_key.id)) || \ + (title_info->meta_key.type == NcmContentMetaType_DataPatch && titleCheckIfDataPatchIdBelongsToApplicationId(app_id, title_info->meta_key.id))) return true; } } @@ -2376,8 +2407,8 @@ static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id) 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) + (title_info->meta_key.type > NcmContentMetaType_BootImagePackageSafe && title_info->meta_key.type < NcmContentMetaType_Application) || \ + title_info->meta_key.type > NcmContentMetaType_DataPatch || !title_info->content_count || !title_info->content_infos) { LOG_MSG_ERROR("Invalid parameters!"); return NULL; @@ -2413,45 +2444,39 @@ static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *pare /* 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; +#define TITLE_DUPLICATE_LINKED_LIST(elem, prnt, prv, nxt) \ + if (title_info->elem) { \ + if (elem) { \ + title_info_dup->elem = elem; \ + } else { \ + title_info_dup->elem = titleDuplicateTitleInfo(title_info->elem, prnt, prv, nxt); \ + if (!title_info_dup->elem) goto end; \ + dup_##elem = true; \ + } \ } - 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; - } +#define TITLE_FREE_DUPLICATED_LINKED_LIST(elem) \ + if (dup_##elem) { \ + tmp1 = title_info_dup->elem; \ + while(tmp1) { \ + tmp2 = tmp1->elem; \ + tmp1->parent = tmp1->previous = tmp1->next = NULL; \ + titleFreeTitleInfo(&tmp1); \ + tmp1 = tmp2; \ + } \ } - 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; - } - } + /* Duplicate linked lists based on two different principles: */ + /* 1) Linked list pointers will only be populated if their corresponding pointer is also populated in the TitleInfo element to duplicate. */ + /* 2) Pointers passed into this function take precedence before actual data duplication. */ + + /* Duplicate parent linked list and update pointer to parent TitleInfo entry -- this will be used while duplicating siblings in the next linked lists. */ + TITLE_DUPLICATE_LINKED_LIST(parent, NULL, NULL, NULL); + if (title_info->parent) parent = title_info_dup->parent; + + /* Duplicate previous and next linked lists. */ + TITLE_DUPLICATE_LINKED_LIST(previous, parent, NULL, title_info_dup); + TITLE_DUPLICATE_LINKED_LIST(next, parent, title_info_dup, NULL); /* Update flag. */ success = true; @@ -2461,53 +2486,30 @@ end: /* 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 (content_infos_dup) free(content_infos_dup); 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(). */ + /* Parent TitleInfo entries are user applications with no reference to child titles, so it's 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 previous and next linked lists (if duplicated). */ + /* We need to take care of not freeing the parent linked list, either because we may have already freed it, or because it may have been passed as an argument. */ + /* Furthermore, both the next pointer from the previous sibling and the previous pointer from the next sibling reference our current duplicated entry. */ + /* To avoid issues, we'll just clear all linked list pointers. */ + TITLE_FREE_DUPLICATED_LINKED_LIST(previous); + TITLE_FREE_DUPLICATED_LINKED_LIST(next); free(title_info_dup); title_info_dup = NULL; } } +#undef TITLE_DUPLICATE_LINKED_LIST + +#undef TITLE_FREE_DUPLICATED_LINKED_LIST + return title_info_dup; } @@ -2536,7 +2538,7 @@ static int titleUserApplicationMetadataEntrySortFunction(const void *a, const vo return strcasecmp(app_metadata_1->lang_entry.name, app_metadata_2->lang_entry.name); } -static int titleOrphanTitleInfoSortFunction(const void *a, const void *b) +static int titleInfoEntrySortFunction(const void *a, const void *b) { const TitleInfo *title_info_1 = *((const TitleInfo**)a); const TitleInfo *title_info_2 = *((const TitleInfo**)b); @@ -2550,6 +2552,24 @@ static int titleOrphanTitleInfoSortFunction(const void *a, const void *b) return 1; } + if (title_info_1->version.value < title_info_2->version.value) + { + return -1; + } else + if (title_info_1->version.value > title_info_2->version.value) + { + return 1; + } + + if (title_info_1->storage_id < title_info_2->storage_id) + { + return -1; + } else + if (title_info_1->storage_id > title_info_2->storage_id) + { + return 1; + } + return 0; }