diff --git a/lib/libnx/include/nx/ContentMetaBinary.h b/lib/libnx/include/nx/ContentMetaBinary.h new file mode 100644 index 0000000..c9f08cb --- /dev/null +++ b/lib/libnx/include/nx/ContentMetaBinary.h @@ -0,0 +1,149 @@ +#pragma once +#include +#include +#include +#include + + +namespace nx +{ + class ContentMetaBinary : + public fnd::ISerialiseableBinary + { + public: + struct ContentInfo + { + crypto::sha::sSha256Hash hash; + byte_t nca_id[cmnt::kContentIdLen]; + size_t size; + cmnt::ContentType type; + + ContentInfo& operator=(const ContentInfo& other) + { + hash = other.hash; + memcpy(nca_id, other.nca_id, cmnt::kContentIdLen); + size = other.size; + type = other.type; + return *this; + } + + bool operator==(const ContentInfo& other) const + { + return (hash == other.hash) \ + && (memcmp(nca_id, other.nca_id, cmnt::kContentIdLen) == 0) \ + && (size == other.size) \ + && (type == other.type); + } + + bool operator!=(const ContentInfo& other) const + { + return !operator==(other); + } + }; + + struct ContentMetaInfo + { + uint64_t id; + uint32_t version; + cmnt::ContentMetaType type; + byte_t attributes; + + ContentMetaInfo& operator=(const ContentMetaInfo& other) + { + id = other.id; + version = other.version; + type = other.type; + attributes = other.attributes; + return *this; + } + + bool operator==(const ContentMetaInfo& other) const + { + return (id == other.id) \ + && (version == other.version) \ + && (type == other.type) \ + && (attributes == other.attributes); + } + + bool operator!=(const ContentMetaInfo& other) const + { + return !operator==(other); + } + }; + + ContentMetaBinary(); + ContentMetaBinary(const ContentMetaBinary& other); + ContentMetaBinary(const byte_t* bytes, size_t len); + + // to be used after export + const byte_t* getBytes() const; + size_t getSize() const; + + // export/import binary + void exportBinary(); + void importBinary(const byte_t* bytes, size_t len); + + // variables + void clear(); + + uint64_t getTitleId() const; + void setTitleId(uint64_t title_id); + + uint32_t getTitleVersion() const; + void setTitleVersion(uint32_t version); + + cmnt::ContentMetaType getType() const; + void setType(cmnt::ContentMetaType type); + + byte_t getAttributes() const; + void setAttributes(byte_t attributes); + + uint32_t getRequiredSystemVersion() const; + void setRequiredSystemVersion(uint32_t version); + + const fnd::List& getContentInfo() const; + void setContentInfo(const fnd::List& info); + + const fnd::List& getContentMetaInfo() const; + void setContentMetaInfo(const fnd::List& info); + + const fnd::MemoryBlob& getExtendedData() const; + void setExtendedData(const fnd::MemoryBlob& data); + + const nx::sDigest& getDigest() const; + void setDigest(const nx::sDigest& digest); + + + private: + const std::string kModuleName = "CONTENT_META_BINARY"; + + // binary blob + fnd::MemoryBlob mBinaryBlob; + + // variables + uint64_t mTitleId; + uint32_t mTitleVersion; + cmnt::ContentMetaType mType; + byte_t mAttributes; + uint32_t mRequiredSystemVersion; + fnd::MemoryBlob mExtendedHeader; + fnd::List mContentInfo; + fnd::List mContentMetaInfo; + fnd::MemoryBlob mExtendedData; + nx::sDigest mDigest; + + inline size_t getExtendedHeaderOffset() const { return sizeof(sContentMetaHeader); } + inline size_t getContentInfoOffset(size_t exhdrSize) const { return getExtendedHeaderOffset() + exhdrSize; } + inline size_t getContentMetaInfoOffset(size_t exhdrSize, size_t contentInfoNum) const { return getContentInfoOffset(exhdrSize) + contentInfoNum * sizeof(sContentInfo); } + inline size_t getExtendedDataOffset(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum) const { return getContentMetaInfoOffset(exhdrSize, contentInfoNum) + contentMetaNum * sizeof(sContentMetaInfo); } + inline size_t getDigestOffset(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum, size_t exdataSize) const { return getExtendedDataOffset(exhdrSize, contentInfoNum, contentMetaNum) + exdataSize; } + inline size_t getTotalSize(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum, size_t exdataSize) const { return getDigestOffset(exhdrSize, contentInfoNum, contentMetaNum, exdataSize) + cmnt::kDigestLen; } + + bool validateExtendedHeaderSize(cmnt::ContentMetaType type, size_t exhdrSize); + size_t getExtendedDataSize(cmnt::ContentMetaType type, const byte_t* data); + void validateBinary(const byte_t* bytes, size_t len); + + bool isEqual(const ContentMetaBinary& other) const; + void copyFrom(const ContentMetaBinary& other); + }; +} \ No newline at end of file diff --git a/lib/libnx/include/nx/cmnt.h b/lib/libnx/include/nx/cmnt.h new file mode 100644 index 0000000..3b37bf0 --- /dev/null +++ b/lib/libnx/include/nx/cmnt.h @@ -0,0 +1,139 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace nx +{ + namespace cmnt + { + enum ContentType + { + TYPE_META = 0, + TYPE_PROGRAM, + TYPE_DATA, + TYPE_CONTROL, + TYPE_HTML_DOCUMENT, + TYPE_LEGAL_INFORMATION, + TYPE_DELTA_FRAGMENT + }; + + enum ContentMetaType + { + METATYPE_SYSTEM_PROGRAM = 1, + METATYPE_SYSTEM_DATA, + METATYPE_SYSTEM_UPDATE, + METATYPE_BOOT_IMAGE_PACKAGE, + METATYPE_BOOT_IMAGE_PACKAGE_SAFE, + + METATYPE_APPLICATION = 0x80, + METATYPE_PATCH, // can have extended data + METATYPE_ADD_ON_CONTENT, + METATYPE_DELTA // can have extended data + }; + + enum UpdateType + { + UPDATETYPE_APPLY_AS_DELTA, + UPDATETYPE_OVERWRITE, + UPDATETYPE_CREATE + }; + + enum ContentMetaAttribute + { + ATTRIBUTE_NONE, + ATTRIBUTE_INCLUDES_EX_FAT_DRIVER, + ATTRIBUTE_REBOOTLESS + }; + + static const uint32_t kRequiredSystemVersion = 335544320; + static const uint32_t kDefaultVersion = 335545344; + static const size_t kContentIdLen = 0x10; + static const size_t kDigestLen = 0x20; + } + + +#pragma pack(push,1) + /* + struct sContentMeta + { + sContentMetaHeader hdr; + byte_t exhdr[]; // optional + sContentInfo info[]; + sContentMetaInfo meta[]; + byte_t extdata[]; + byte_t digest[32] + }; + */ + + struct sContentMetaHeader + { + le_uint64_t id; + le_uint32_t version; + byte_t type; + byte_t reserved_0; + le_uint16_t exhdr_size; + le_uint16_t content_count; + le_uint16_t content_meta_count; + byte_t attributes; + byte_t reserved_1[3]; + le_uint32_t required_system_version; + byte_t reserved_2[4]; + }; + + struct sContentInfo + { + crypto::sha::sSha256Hash content_hash; + byte_t content_id[cmnt::kContentIdLen]; + le_uint32_t size_lower; + le_uint16_t size_higher; + byte_t content_type; + byte_t id_offset; + }; + + struct sContentMetaInfo + { + le_uint64_t id; + le_uint32_t version; + byte_t type; + byte_t attributes; + byte_t reserved[2]; + }; + + struct sApplicationMetaExtendedHeader + { + le_uint64_t id; + le_uint32_t required_system_version; + byte_t reserved[4]; + }; + + struct sPatchMetaExtendedHeader + { + le_uint64_t id; + le_uint32_t required_system_version; + le_uint32_t extended_data_size; + byte_t reserved[8]; + }; + + struct sAddOnContentMetaExtendedHeader + { + le_uint64_t id; + le_uint32_t required_system_version; + byte_t reserved[4]; + }; + + struct sDeltaMetaExtendedHeader + { + le_uint64_t id; + le_uint32_t extended_data_size; + byte_t reserved[4]; + }; + + struct sDigest + { + byte_t data[cmnt::kDigestLen]; + }; +#pragma pack(pop) +} \ No newline at end of file diff --git a/lib/libnx/nx.vcxproj b/lib/libnx/nx.vcxproj index 2f184e6..5d097a6 100644 --- a/lib/libnx/nx.vcxproj +++ b/lib/libnx/nx.vcxproj @@ -24,6 +24,8 @@ + + @@ -68,6 +70,7 @@ + diff --git a/lib/libnx/nx.vcxproj.filters b/lib/libnx/nx.vcxproj.filters index 0d0c91d..6710ddb 100644 --- a/lib/libnx/nx.vcxproj.filters +++ b/lib/libnx/nx.vcxproj.filters @@ -144,6 +144,12 @@ Header Files + + Header Files + + + Header Files + @@ -245,6 +251,9 @@ Source Files + + Source Files + diff --git a/lib/libnx/source/ContentMetaBinary.cpp b/lib/libnx/source/ContentMetaBinary.cpp new file mode 100644 index 0000000..003da2f --- /dev/null +++ b/lib/libnx/source/ContentMetaBinary.cpp @@ -0,0 +1,300 @@ +#include + +nx::ContentMetaBinary::ContentMetaBinary() +{ + clear(); +} + +nx::ContentMetaBinary::ContentMetaBinary(const ContentMetaBinary & other) +{ + copyFrom(other); +} + +nx::ContentMetaBinary::ContentMetaBinary(const byte_t * bytes, size_t len) +{ + importBinary(bytes, len); +} + +const byte_t * nx::ContentMetaBinary::getBytes() const +{ + return mBinaryBlob.getBytes(); +} + +size_t nx::ContentMetaBinary::getSize() const +{ + return mBinaryBlob.getSize(); +} + +void nx::ContentMetaBinary::exportBinary() +{ + throw fnd::Exception(kModuleName, "exportBinary() not implemented"); +} + +void nx::ContentMetaBinary::importBinary(const byte_t * bytes, size_t len) +{ + // clear member variables + clear(); + + // validate layout + validateBinary(bytes, len); + + // get pointer to header structure + const sContentMetaHeader* hdr = (const sContentMetaHeader*)bytes; + + mTitleId = hdr->id.get(); + mTitleVersion = hdr->version.get(); + mType = (cmnt::ContentMetaType)hdr->type; + mAttributes = hdr->attributes; + mRequiredSystemVersion = hdr->required_system_version.get(); + size_t exdata_size = 0; + + // save exheader + if (hdr->exhdr_size.get() > 0) + { + mExtendedHeader.alloc(hdr->exhdr_size.get()); + memcpy(mExtendedHeader.getBytes(), bytes + getExtendedHeaderOffset(), hdr->exhdr_size.get()); + + exdata_size = getExtendedDataSize(mType, mExtendedHeader.getBytes()); + } + + // save content info + if (hdr->content_count.get() > 0) + { + const sContentInfo* info = (const sContentInfo*)(bytes + getContentInfoOffset(hdr->exhdr_size.get())); + for (size_t i = 0; i < hdr->content_count.get(); i++) + { + mContentInfo[i].hash = info[i].content_hash; + memcpy(mContentInfo[i].nca_id, info[i].content_id, cmnt::kContentIdLen); + mContentInfo[i].size = (uint64_t)(info[i].size_lower.get()) | (uint64_t)(info[i].size_higher.get()) << 32; + mContentInfo[i].type = (cmnt::ContentType)info[i].content_type; + } + } + + // save content meta info + if (hdr->content_meta_count.get() > 0) + { + const sContentMetaInfo* info = (const sContentMetaInfo*)(bytes + getContentMetaInfoOffset(hdr->exhdr_size.get(), hdr->content_count.get())); + for (size_t i = 0; i < hdr->content_meta_count.get(); i++) + { + mContentMetaInfo[i].id = info[i].id.get(); + mContentMetaInfo[i].version = info[i].version.get(); + mContentMetaInfo[i].type = (cmnt::ContentMetaType)info[i].type; + mContentMetaInfo[i].attributes = info[i].attributes; + } + } + + // save exdata + if (exdata_size > 0) + { + mExtendedData.alloc(exdata_size); + memcpy(mExtendedData.getBytes(), bytes + getExtendedDataOffset(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get()), exdata_size); + } + + // save digest + memcpy(mDigest.data, bytes + getDigestOffset(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), exdata_size), cmnt::kDigestLen); + +} + +void nx::ContentMetaBinary::clear() +{ + mBinaryBlob.clear(); + mTitleId = 0; + mTitleVersion = 0; + mType = cmnt::METATYPE_SYSTEM_PROGRAM; + mAttributes = cmnt::ATTRIBUTE_NONE; + mRequiredSystemVersion = 0; + mExtendedHeader.clear(); + mContentInfo.clear(); + mContentMetaInfo.clear(); + mExtendedData.clear(); + memset(mDigest.data, 0, cmnt::kDigestLen); +} + +uint64_t nx::ContentMetaBinary::getTitleId() const +{ + return mTitleId; +} + +void nx::ContentMetaBinary::setTitleId(uint64_t title_id) +{ + mTitleId = title_id; +} + +uint32_t nx::ContentMetaBinary::getTitleVersion() const +{ + return mTitleVersion; +} + +void nx::ContentMetaBinary::setTitleVersion(uint32_t version) +{ + mTitleVersion = version; +} + +nx::cmnt::ContentMetaType nx::ContentMetaBinary::getType() const +{ + return mType; +} + +void nx::ContentMetaBinary::setType(cmnt::ContentMetaType type) +{ + mType = type; +} + +byte_t nx::ContentMetaBinary::getAttributes() const +{ + return mAttributes; +} + +void nx::ContentMetaBinary::setAttributes(byte_t attributes) +{ + mAttributes = attributes; +} + +uint32_t nx::ContentMetaBinary::getRequiredSystemVersion() const +{ + return mRequiredSystemVersion; +} + +void nx::ContentMetaBinary::setRequiredSystemVersion(uint32_t version) +{ + mRequiredSystemVersion = version; +} + +const fnd::List& nx::ContentMetaBinary::getContentInfo() const +{ + return mContentInfo; +} + +void nx::ContentMetaBinary::setContentInfo(const fnd::List& info) +{ + mContentInfo = info; +} + +const fnd::List& nx::ContentMetaBinary::getContentMetaInfo() const +{ + return mContentMetaInfo; +} + +void nx::ContentMetaBinary::setContentMetaInfo(const fnd::List& info) +{ + mContentMetaInfo = info; +} + +const fnd::MemoryBlob & nx::ContentMetaBinary::getExtendedData() const +{ + return mExtendedData; +} + +void nx::ContentMetaBinary::setExtendedData(const fnd::MemoryBlob & data) +{ + mExtendedData = data; +} + +const nx::sDigest & nx::ContentMetaBinary::getDigest() const +{ + return mDigest; +} + +void nx::ContentMetaBinary::setDigest(const nx::sDigest & digest) +{ + + memcpy(mDigest.data, digest.data, cmnt::kDigestLen); +} + +bool nx::ContentMetaBinary::validateExtendedHeaderSize(cmnt::ContentMetaType type, size_t exhdrSize) +{ + bool validSize = false; + + if (type == cmnt::METATYPE_APPLICATION && exhdrSize == sizeof(sApplicationMetaExtendedHeader)) + validSize = true; + else if (type == cmnt::METATYPE_PATCH && exhdrSize == sizeof(sPatchMetaExtendedHeader)) + validSize = true; + else if (type == cmnt::METATYPE_ADD_ON_CONTENT && exhdrSize == sizeof(sAddOnContentMetaExtendedHeader)) + validSize = true; + else if (type == cmnt::METATYPE_DELTA && exhdrSize == sizeof(sDeltaMetaExtendedHeader)) + validSize = true; + + return validSize; +} + +size_t nx::ContentMetaBinary::getExtendedDataSize(cmnt::ContentMetaType type, const byte_t * data) +{ + size_t exdata_len = 0; + if (type == cmnt::METATYPE_PATCH) + { + const sPatchMetaExtendedHeader* exhdr = (const sPatchMetaExtendedHeader*)(data); + exdata_len = exhdr->extended_data_size.get(); + } + else if (type == cmnt::METATYPE_DELTA) + { + const sDeltaMetaExtendedHeader* exhdr = (const sDeltaMetaExtendedHeader*)(data); + exdata_len = exhdr->extended_data_size.get(); + } + return exdata_len; +} + +void nx::ContentMetaBinary::validateBinary(const byte_t * bytes, size_t len) +{ + // check if it is large enough to read the header + if (len < sizeof(sContentMetaHeader)) + { + throw fnd::Exception(kModuleName, "Binary too small"); + } + + // get pointer to header structure + const sContentMetaHeader* hdr = (const sContentMetaHeader*)bytes; + + // validate extended header size + if (validateExtendedHeaderSize((cmnt::ContentMetaType)hdr->type, hdr->exhdr_size.get()) == false) + { + throw fnd::Exception(kModuleName, "Invalid extended header size"); + } + + // check binary size again for new minimum size + if (len < getTotalSize(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), 0)) + { + throw fnd::Exception(kModuleName, "Binary too small"); + } + + // check binary size again with extended data size + if (len < getTotalSize(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), getExtendedDataSize((cmnt::ContentMetaType)hdr->type, bytes + getExtendedHeaderOffset()))) + { + throw fnd::Exception(kModuleName, "Binary too small"); + } +} + +bool nx::ContentMetaBinary::isEqual(const ContentMetaBinary & other) const +{ + return (mTitleId == other.mTitleId) \ + && (mTitleVersion == other.mTitleVersion) \ + && (mType == other.mType) \ + && (mAttributes == other.mAttributes) \ + && (mRequiredSystemVersion == other.mRequiredSystemVersion) \ + && (mExtendedHeader == other.mExtendedHeader) \ + && (mContentInfo == other.mContentInfo) \ + && (mContentMetaInfo == other.mContentMetaInfo) \ + && (mExtendedData == other.mExtendedData) \ + && (memcmp(mDigest.data, other.mDigest.data, cmnt::kDigestLen) == 0); +} + +void nx::ContentMetaBinary::copyFrom(const ContentMetaBinary & other) +{ + if (other.getSize() > 0) + { + importBinary(other.getBytes(), other.getSize()); + } + else + { + clear(); + mTitleId = other.mTitleId; + mTitleVersion = other.mTitleVersion; + mType = other.mType; + mAttributes = other.mAttributes; + mRequiredSystemVersion = other.mRequiredSystemVersion; + mExtendedHeader = other.mExtendedHeader; + mContentInfo = other.mContentInfo; + mContentMetaInfo = other.mContentMetaInfo; + mExtendedData = other.mExtendedData; + memcpy(mDigest.data, other.mDigest.data, cmnt::kDigestLen); + } +}