Preliminar 15.x support.

This commit uses my yet unmerged libnx PR to update ncm_types.h.

PoC code hasn't been updated yet, so proper support for DLC updates will arrive at a later time.

Note to self: implement a way to provide access to loaded DataPatch TitleInfo entries (linked list hell).

* bktr: renamed bktrBucketInitializeSubStorageReadParams to bktrInitializeSubStorageReadParams to avoid redundancy, added debug code to dump BucketInfo and BucketTree tables if BucketTree storage initialization fails.

* cnmt: updated ContentMetaAddOnContentMetaExtendedHeader struct to its 15.x equivalent, added ContentMetaLegacyAddOnContentMetaExtendedHeader struct, added ContentMetaDataPatchMetaExtendedHeader struct, updated the cnmtGetRequiredTitleId and cnmtGetRequiredTitleVersion functions to support DataPatch titles, updated cnmtInitializeContext to support both the new AddOnContent extended header and DataPatch titles, added debug code to dump the whole CNMT if context initialization fails, updated cnmtGenerateAuthoringToolXml to support DataPatch titles.

* keys: updated block hashes to match 15.x keyset, use case-insensitive comparison while looking for entry names in keysReadKeysFromFile, make sure the eticket_rsa_kek is non-zero before proceeding in keysGetDecryptedEticketRsaDeviceKey.

* nca: updated NcaKeyGeneration enum, added reminder about updating NcaSignatureKeyGeneration if necessary, replaced ncaFsSectionCheckHashRegionAccess with ncaFsSectionCheckPlaintextHashRegionAccess, removed all extents checks on Patch RomFS and sparse sections, updated ncaGetFsSectionTypeName to reflect if a FS section holds a sparse layer or not.

* nca_storage: updated ncaStorageInitializeContext to avoid initializing a compressed storage if a sparse layer is also used (fixes issues with Them's Fightin' Herds), updated ncaStorageSetPatchOriginalSubStorage to enforce the presence of a compressed storage in a patch if the base FS holds a compressed storage.

* npdm: added reminder about updating NpdmSignatureKeyGeneration if necessary, updated NpdmFsAccessControlFlags enum, updated NpdmAccessibility enum, updated NpdmSystemCallId enum, fixed typos.

* title: updated all relevant functions that deal with NcmContentMetaType values to also handle DataPatch titles, added functions to handle DataPatchId values, removed titleConvertNcmContentSizeToU64 and titleConvertU64ToNcmContentSize functions in favor of ncmContentInfoSizeToU64 and ncmU64ToContentInfoSize from my unmerged libnx PR, updated internal arrays to match 15.x changes, renamed titleOrphanTitleInfoSortFunction to titleInfoEntrySortFunction and updated it to also sort entries by version and storage ID, updated titleGenerateTitleInfoEntriesForTitleStorage to sort TitleInfo entries, simplified titleDuplicateTitleInfo a bit by using macros.
This commit is contained in:
Pablo Curiel 2022-10-23 16:44:47 +02:00
parent 00dd3df4fc
commit 0f1055c84e
11 changed files with 695 additions and 497 deletions

View File

@ -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();
}
}
}

View File

@ -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

View File

@ -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.

View File

@ -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);

View File

@ -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;

View File

@ -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(&params, compressed_table, compression_table_offset, compressed_bucket->size, 0, 0, false, BucketTreeSubStorageType_Compressed);
bktrInitializeSubStorageReadParams(&params, compressed_table, compression_table_offset, compressed_bucket->size, 0, 0, false, BucketTreeSubStorageType_Compressed);
if (!bktrReadSubStorage(substorage, &params))
{
@ -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(&params, out, data_offset, read_size, offset, 0, false, ctx->storage_type);
bktrInitializeSubStorageReadParams(&params, 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]), &params);
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(&params, out, offset, read_size, 0, cur_entry.generation, cur_entry.encryption == BucketTreeAesCtrExStorageEncryption_Enabled, ctx->storage_type);
bktrInitializeSubStorageReadParams(&params, out, offset, read_size, 0, cur_entry.generation, cur_entry.encryption == BucketTreeAesCtrExStorageEncryption_Enabled, ctx->storage_type);
success = bktrReadSubStorage(&(ctx->substorages[0]), &params);
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(&params, out, data_offset, read_size, 0, 0, false, ctx->storage_type);
bktrInitializeSubStorageReadParams(&params, out, data_offset, read_size, 0, 0, false, ctx->storage_type);
success = bktrReadSubStorage(&(ctx->substorages[0]), &params);
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(&params, read_ptr, data_offset, compressed_data_size, 0, 0, false, ctx->storage_type);
bktrInitializeSubStorageReadParams(&params, read_ptr, data_offset, compressed_data_size, 0, 0, false, ctx->storage_type);
/* Read compressed LZ4 block. */
if (!bktrReadSubStorage(&(ctx->substorages[0]), &params))
@ -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;

View File

@ -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_
" <Version>%u</Version>\n" \
" <ReleaseVersion>%u</ReleaseVersion>\n" \
" <PrivateVersion>%u</PrivateVersion>\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(" <RequiredApplicationVersion>%u</RequiredApplicationVersion>\n", \
((ContentMetaApplicationMetaExtendedHeader*)cnmt_ctx->extended_header)->required_application_version.value)) goto end;
}
if (content_meta_type == NcmContentMetaType_Application && \
!CNMT_ADD_FMT_STR(" <RequiredApplicationVersion>%u</RequiredApplicationVersion>\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(" <DataPatchId>0x%016lx</DataPatchId>\n", ((ContentMetaAddOnContentMetaExtendedHeader*)cnmt_ctx->extended_header)->data_patch_id)) goto end;
/* DataId (DataPatch). */
if (content_meta_type == NcmContentMetaType_DataPatch && \
!CNMT_ADD_FMT_STR(" <DataId>0x%016lx</DataId>\n", ((ContentMetaDataPatchMetaExtendedHeader*)cnmt_ctx->extended_header)->data_id)) goto end;
if (!(success = CNMT_ADD_FMT_STR("</ContentMeta>"))) 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:

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}