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%s>\n" \
" <%s>0x%016lx%s>\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;
}