[nx|nstool] Added support for reading ContentMeta (.cnmt).

This commit is contained in:
jakcron 2018-05-12 23:02:53 +08:00
parent b36875661e
commit 79c24153bb
21 changed files with 589 additions and 190 deletions

View file

@ -2,7 +2,7 @@
#include <string> #include <string>
#include <fnd/MemoryBlob.h> #include <fnd/MemoryBlob.h>
#include <fnd/List.h> #include <fnd/List.h>
#include <nx/cmnt.h> #include <nx/cnmt.h>
namespace nx namespace nx
@ -14,14 +14,14 @@ namespace nx
struct ContentInfo struct ContentInfo
{ {
crypto::sha::sSha256Hash hash; crypto::sha::sSha256Hash hash;
byte_t nca_id[cmnt::kContentIdLen]; byte_t nca_id[cnmt::kContentIdLen];
size_t size; size_t size;
cmnt::ContentType type; cnmt::ContentType type;
ContentInfo& operator=(const ContentInfo& other) ContentInfo& operator=(const ContentInfo& other)
{ {
hash = other.hash; hash = other.hash;
memcpy(nca_id, other.nca_id, cmnt::kContentIdLen); memcpy(nca_id, other.nca_id, cnmt::kContentIdLen);
size = other.size; size = other.size;
type = other.type; type = other.type;
return *this; return *this;
@ -30,7 +30,7 @@ namespace nx
bool operator==(const ContentInfo& other) const bool operator==(const ContentInfo& other) const
{ {
return (hash == other.hash) \ return (hash == other.hash) \
&& (memcmp(nca_id, other.nca_id, cmnt::kContentIdLen) == 0) \ && (memcmp(nca_id, other.nca_id, cnmt::kContentIdLen) == 0) \
&& (size == other.size) \ && (size == other.size) \
&& (type == other.type); && (type == other.type);
} }
@ -45,7 +45,7 @@ namespace nx
{ {
uint64_t id; uint64_t id;
uint32_t version; uint32_t version;
cmnt::ContentMetaType type; cnmt::ContentMetaType type;
byte_t attributes; byte_t attributes;
ContentMetaInfo& operator=(const ContentMetaInfo& other) ContentMetaInfo& operator=(const ContentMetaInfo& other)
@ -71,6 +71,99 @@ namespace nx
} }
}; };
struct ApplicationMetaExtendedHeader
{
uint64_t patch_id;
uint32_t required_system_version;
ApplicationMetaExtendedHeader& operator=(const ApplicationMetaExtendedHeader& other)
{
patch_id = other.patch_id;
required_system_version = other.required_system_version;
return *this;
}
bool operator==(const ApplicationMetaExtendedHeader& other) const
{
return (patch_id == other.patch_id) \
&& (required_system_version == other.required_system_version);
}
bool operator!=(const ApplicationMetaExtendedHeader& other) const
{
return !operator==(other);
}
};
struct PatchMetaExtendedHeader
{
uint64_t application_id;
uint32_t required_system_version;
PatchMetaExtendedHeader& operator=(const PatchMetaExtendedHeader& other)
{
application_id = other.application_id;
required_system_version = other.required_system_version;
return *this;
}
bool operator==(const PatchMetaExtendedHeader& other) const
{
return (application_id == other.application_id) \
&& (required_system_version == other.required_system_version);
}
bool operator!=(const PatchMetaExtendedHeader& other) const
{
return !operator==(other);
}
};
struct AddOnContentMetaExtendedHeader
{
uint64_t application_id;
uint32_t required_system_version;
AddOnContentMetaExtendedHeader& operator=(const AddOnContentMetaExtendedHeader& other)
{
application_id = other.application_id;
required_system_version = other.required_system_version;
return *this;
}
bool operator==(const AddOnContentMetaExtendedHeader& other) const
{
return (application_id == other.application_id) \
&& (required_system_version == other.required_system_version);
}
bool operator!=(const AddOnContentMetaExtendedHeader& other) const
{
return !operator==(other);
}
};
struct DeltaMetaExtendedHeader
{
uint64_t application_id;
DeltaMetaExtendedHeader& operator=(const DeltaMetaExtendedHeader& other)
{
application_id = other.application_id;
return *this;
}
bool operator==(const DeltaMetaExtendedHeader& other) const
{
return (application_id == other.application_id);
}
bool operator!=(const DeltaMetaExtendedHeader& other) const
{
return !operator==(other);
}
};
ContentMetaBinary(); ContentMetaBinary();
ContentMetaBinary(const ContentMetaBinary& other); ContentMetaBinary(const ContentMetaBinary& other);
ContentMetaBinary(const byte_t* bytes, size_t len); ContentMetaBinary(const byte_t* bytes, size_t len);
@ -92,14 +185,26 @@ namespace nx
uint32_t getTitleVersion() const; uint32_t getTitleVersion() const;
void setTitleVersion(uint32_t version); void setTitleVersion(uint32_t version);
cmnt::ContentMetaType getType() const; cnmt::ContentMetaType getType() const;
void setType(cmnt::ContentMetaType type); void setType(cnmt::ContentMetaType type);
byte_t getAttributes() const; byte_t getAttributes() const;
void setAttributes(byte_t attributes); void setAttributes(byte_t attributes);
uint32_t getRequiredSystemVersion() const; uint32_t getRequiredDownloadSystemVersion() const;
void setRequiredSystemVersion(uint32_t version); void setRequiredDownloadSystemVersion(uint32_t version);
const ApplicationMetaExtendedHeader& getApplicationMetaExtendedHeader() const;
void setApplicationMetaExtendedHeader(const ApplicationMetaExtendedHeader& exhdr);
const PatchMetaExtendedHeader& getPatchMetaExtendedHeader() const;
void setPatchMetaExtendedHeader(const PatchMetaExtendedHeader& exhdr);
const AddOnContentMetaExtendedHeader& getAddOnContentMetaExtendedHeader() const;
void setAddOnContentMetaExtendedHeader(const AddOnContentMetaExtendedHeader& exhdr);
const DeltaMetaExtendedHeader& getDeltaMetaExtendedHeader() const;
void setDeltaMetaExtendedHeader(const DeltaMetaExtendedHeader& exhdr);
const fnd::List<nx::ContentMetaBinary::ContentInfo>& getContentInfo() const; const fnd::List<nx::ContentMetaBinary::ContentInfo>& getContentInfo() const;
void setContentInfo(const fnd::List<nx::ContentMetaBinary::ContentInfo>& info); void setContentInfo(const fnd::List<nx::ContentMetaBinary::ContentInfo>& info);
@ -123,10 +228,16 @@ namespace nx
// variables // variables
uint64_t mTitleId; uint64_t mTitleId;
uint32_t mTitleVersion; uint32_t mTitleVersion;
cmnt::ContentMetaType mType; cnmt::ContentMetaType mType;
byte_t mAttributes; byte_t mAttributes;
uint32_t mRequiredSystemVersion; uint32_t mRequiredDownloadSystemVersion;
fnd::MemoryBlob mExtendedHeader; fnd::MemoryBlob mExtendedHeader;
ApplicationMetaExtendedHeader mApplicationMetaExtendedHeader;
PatchMetaExtendedHeader mPatchMetaExtendedHeader;
AddOnContentMetaExtendedHeader mAddOnContentMetaExtendedHeader;
DeltaMetaExtendedHeader mDeltaMetaExtendedHeader;
fnd::List<nx::ContentMetaBinary::ContentInfo> mContentInfo; fnd::List<nx::ContentMetaBinary::ContentInfo> mContentInfo;
fnd::List<nx::ContentMetaBinary::ContentMetaInfo> mContentMetaInfo; fnd::List<nx::ContentMetaBinary::ContentMetaInfo> mContentMetaInfo;
fnd::MemoryBlob mExtendedData; fnd::MemoryBlob mExtendedData;
@ -137,11 +248,11 @@ namespace nx
inline size_t getContentMetaInfoOffset(size_t exhdrSize, size_t contentInfoNum) const { return getContentInfoOffset(exhdrSize) + contentInfoNum * sizeof(sContentInfo); } 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 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 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; } inline size_t getTotalSize(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum, size_t exdataSize) const { return getDigestOffset(exhdrSize, contentInfoNum, contentMetaNum, exdataSize) + cnmt::kDigestLen; }
bool validateExtendedHeaderSize(cmnt::ContentMetaType type, size_t exhdrSize); bool validateExtendedHeaderSize(cnmt::ContentMetaType type, size_t exhdrSize) const;
size_t getExtendedDataSize(cmnt::ContentMetaType type, const byte_t* data); size_t getExtendedDataSize(cnmt::ContentMetaType type, const byte_t* data) const;
void validateBinary(const byte_t* bytes, size_t len); void validateBinary(const byte_t* bytes, size_t len) const;
bool isEqual(const ContentMetaBinary& other) const; bool isEqual(const ContentMetaBinary& other) const;
void copyFrom(const ContentMetaBinary& other); void copyFrom(const ContentMetaBinary& other);

View file

@ -7,7 +7,7 @@
namespace nx namespace nx
{ {
namespace cmnt namespace cnmt
{ {
enum ContentType enum ContentType
{ {
@ -43,7 +43,6 @@ namespace nx
enum ContentMetaAttribute enum ContentMetaAttribute
{ {
ATTRIBUTE_NONE,
ATTRIBUTE_INCLUDES_EX_FAT_DRIVER, ATTRIBUTE_INCLUDES_EX_FAT_DRIVER,
ATTRIBUTE_REBOOTLESS ATTRIBUTE_REBOOTLESS
}; };
@ -79,14 +78,14 @@ namespace nx
le_uint16_t content_meta_count; le_uint16_t content_meta_count;
byte_t attributes; byte_t attributes;
byte_t reserved_1[3]; byte_t reserved_1[3];
le_uint32_t required_system_version; le_uint32_t required_download_system_version;
byte_t reserved_2[4]; byte_t reserved_2[4];
}; };
struct sContentInfo struct sContentInfo
{ {
crypto::sha::sSha256Hash content_hash; crypto::sha::sSha256Hash content_hash;
byte_t content_id[cmnt::kContentIdLen]; byte_t content_id[cnmt::kContentIdLen];
le_uint32_t size_lower; le_uint32_t size_lower;
le_uint16_t size_higher; le_uint16_t size_higher;
byte_t content_type; byte_t content_type;
@ -104,14 +103,14 @@ namespace nx
struct sApplicationMetaExtendedHeader struct sApplicationMetaExtendedHeader
{ {
le_uint64_t id; le_uint64_t patch_id;
le_uint32_t required_system_version; le_uint32_t required_system_version;
byte_t reserved[4]; byte_t reserved[4];
}; };
struct sPatchMetaExtendedHeader struct sPatchMetaExtendedHeader
{ {
le_uint64_t id; le_uint64_t application_id;
le_uint32_t required_system_version; le_uint32_t required_system_version;
le_uint32_t extended_data_size; le_uint32_t extended_data_size;
byte_t reserved[8]; byte_t reserved[8];
@ -119,21 +118,21 @@ namespace nx
struct sAddOnContentMetaExtendedHeader struct sAddOnContentMetaExtendedHeader
{ {
le_uint64_t id; le_uint64_t application_id;
le_uint32_t required_system_version; le_uint32_t required_system_version;
byte_t reserved[4]; byte_t reserved[4];
}; };
struct sDeltaMetaExtendedHeader struct sDeltaMetaExtendedHeader
{ {
le_uint64_t id; le_uint64_t application_id;
le_uint32_t extended_data_size; le_uint32_t extended_data_size;
byte_t reserved[4]; byte_t reserved[4];
}; };
struct sDigest struct sDigest
{ {
byte_t data[cmnt::kDigestLen]; byte_t data[cnmt::kDigestLen];
}; };
#pragma pack(pop) #pragma pack(pop)
} }

View file

@ -43,9 +43,9 @@ void nx::ContentMetaBinary::importBinary(const byte_t * bytes, size_t len)
mTitleId = hdr->id.get(); mTitleId = hdr->id.get();
mTitleVersion = hdr->version.get(); mTitleVersion = hdr->version.get();
mType = (cmnt::ContentMetaType)hdr->type; mType = (cnmt::ContentMetaType)hdr->type;
mAttributes = hdr->attributes; mAttributes = hdr->attributes;
mRequiredSystemVersion = hdr->required_system_version.get(); mRequiredDownloadSystemVersion = hdr->required_download_system_version.get();
size_t exdata_size = 0; size_t exdata_size = 0;
// save exheader // save exheader
@ -54,6 +54,27 @@ void nx::ContentMetaBinary::importBinary(const byte_t * bytes, size_t len)
mExtendedHeader.alloc(hdr->exhdr_size.get()); mExtendedHeader.alloc(hdr->exhdr_size.get());
memcpy(mExtendedHeader.getBytes(), bytes + getExtendedHeaderOffset(), hdr->exhdr_size.get()); memcpy(mExtendedHeader.getBytes(), bytes + getExtendedHeaderOffset(), hdr->exhdr_size.get());
switch (mType)
{
case (cnmt::METATYPE_APPLICATION):
mApplicationMetaExtendedHeader.patch_id = ((sApplicationMetaExtendedHeader*)mExtendedHeader.getBytes())->patch_id.get();
mApplicationMetaExtendedHeader.required_system_version = ((sApplicationMetaExtendedHeader*)mExtendedHeader.getBytes())->required_system_version.get();
break;
case (cnmt::METATYPE_PATCH):
mPatchMetaExtendedHeader.application_id = ((sPatchMetaExtendedHeader*)mExtendedHeader.getBytes())->application_id.get();
mPatchMetaExtendedHeader.required_system_version = ((sPatchMetaExtendedHeader*)mExtendedHeader.getBytes())->required_system_version.get();
break;
case (cnmt::METATYPE_ADD_ON_CONTENT):
mAddOnContentMetaExtendedHeader.application_id = ((sAddOnContentMetaExtendedHeader*)mExtendedHeader.getBytes())->application_id.get();
mAddOnContentMetaExtendedHeader.required_system_version = ((sAddOnContentMetaExtendedHeader*)mExtendedHeader.getBytes())->required_system_version.get();
break;
case (cnmt::METATYPE_DELTA):
mDeltaMetaExtendedHeader.application_id = ((sDeltaMetaExtendedHeader*)mExtendedHeader.getBytes())->application_id.get();
break;
default:
break;
}
exdata_size = getExtendedDataSize(mType, mExtendedHeader.getBytes()); exdata_size = getExtendedDataSize(mType, mExtendedHeader.getBytes());
} }
@ -64,9 +85,9 @@ void nx::ContentMetaBinary::importBinary(const byte_t * bytes, size_t len)
for (size_t i = 0; i < hdr->content_count.get(); i++) for (size_t i = 0; i < hdr->content_count.get(); i++)
{ {
mContentInfo[i].hash = info[i].content_hash; mContentInfo[i].hash = info[i].content_hash;
memcpy(mContentInfo[i].nca_id, info[i].content_id, cmnt::kContentIdLen); memcpy(mContentInfo[i].nca_id, info[i].content_id, cnmt::kContentIdLen);
mContentInfo[i].size = (uint64_t)(info[i].size_lower.get()) | (uint64_t)(info[i].size_higher.get()) << 32; 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; mContentInfo[i].type = (cnmt::ContentType)info[i].content_type;
} }
} }
@ -78,7 +99,7 @@ void nx::ContentMetaBinary::importBinary(const byte_t * bytes, size_t len)
{ {
mContentMetaInfo[i].id = info[i].id.get(); mContentMetaInfo[i].id = info[i].id.get();
mContentMetaInfo[i].version = info[i].version.get(); mContentMetaInfo[i].version = info[i].version.get();
mContentMetaInfo[i].type = (cmnt::ContentMetaType)info[i].type; mContentMetaInfo[i].type = (cnmt::ContentMetaType)info[i].type;
mContentMetaInfo[i].attributes = info[i].attributes; mContentMetaInfo[i].attributes = info[i].attributes;
} }
} }
@ -91,7 +112,7 @@ void nx::ContentMetaBinary::importBinary(const byte_t * bytes, size_t len)
} }
// save digest // save digest
memcpy(mDigest.data, bytes + getDigestOffset(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), exdata_size), cmnt::kDigestLen); memcpy(mDigest.data, bytes + getDigestOffset(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), exdata_size), cnmt::kDigestLen);
} }
@ -100,14 +121,18 @@ void nx::ContentMetaBinary::clear()
mBinaryBlob.clear(); mBinaryBlob.clear();
mTitleId = 0; mTitleId = 0;
mTitleVersion = 0; mTitleVersion = 0;
mType = cmnt::METATYPE_SYSTEM_PROGRAM; mType = cnmt::METATYPE_SYSTEM_PROGRAM;
mAttributes = cmnt::ATTRIBUTE_NONE; mAttributes = 0;
mRequiredSystemVersion = 0; mRequiredDownloadSystemVersion = 0;
mExtendedHeader.clear(); mExtendedHeader.clear();
memset(&mApplicationMetaExtendedHeader, 0, sizeof(mApplicationMetaExtendedHeader));
memset(&mPatchMetaExtendedHeader, 0, sizeof(mPatchMetaExtendedHeader));
memset(&mAddOnContentMetaExtendedHeader, 0, sizeof(mAddOnContentMetaExtendedHeader));
memset(&mDeltaMetaExtendedHeader, 0, sizeof(mDeltaMetaExtendedHeader));
mContentInfo.clear(); mContentInfo.clear();
mContentMetaInfo.clear(); mContentMetaInfo.clear();
mExtendedData.clear(); mExtendedData.clear();
memset(mDigest.data, 0, cmnt::kDigestLen); memset(mDigest.data, 0, cnmt::kDigestLen);
} }
uint64_t nx::ContentMetaBinary::getTitleId() const uint64_t nx::ContentMetaBinary::getTitleId() const
@ -130,12 +155,12 @@ void nx::ContentMetaBinary::setTitleVersion(uint32_t version)
mTitleVersion = version; mTitleVersion = version;
} }
nx::cmnt::ContentMetaType nx::ContentMetaBinary::getType() const nx::cnmt::ContentMetaType nx::ContentMetaBinary::getType() const
{ {
return mType; return mType;
} }
void nx::ContentMetaBinary::setType(cmnt::ContentMetaType type) void nx::ContentMetaBinary::setType(cnmt::ContentMetaType type)
{ {
mType = type; mType = type;
} }
@ -150,14 +175,54 @@ void nx::ContentMetaBinary::setAttributes(byte_t attributes)
mAttributes = attributes; mAttributes = attributes;
} }
uint32_t nx::ContentMetaBinary::getRequiredSystemVersion() const uint32_t nx::ContentMetaBinary::getRequiredDownloadSystemVersion() const
{ {
return mRequiredSystemVersion; return mRequiredDownloadSystemVersion;
} }
void nx::ContentMetaBinary::setRequiredSystemVersion(uint32_t version) void nx::ContentMetaBinary::setRequiredDownloadSystemVersion(uint32_t version)
{ {
mRequiredSystemVersion = version; mRequiredDownloadSystemVersion = version;
}
const nx::ContentMetaBinary::ApplicationMetaExtendedHeader& nx::ContentMetaBinary::getApplicationMetaExtendedHeader() const
{
return mApplicationMetaExtendedHeader;
}
void nx::ContentMetaBinary::setApplicationMetaExtendedHeader(const ApplicationMetaExtendedHeader& exhdr)
{
mApplicationMetaExtendedHeader = exhdr;
}
const nx::ContentMetaBinary::PatchMetaExtendedHeader& nx::ContentMetaBinary::getPatchMetaExtendedHeader() const
{
return mPatchMetaExtendedHeader;
}
void nx::ContentMetaBinary::setPatchMetaExtendedHeader(const PatchMetaExtendedHeader& exhdr)
{
mPatchMetaExtendedHeader = exhdr;
}
const nx::ContentMetaBinary::AddOnContentMetaExtendedHeader& nx::ContentMetaBinary::getAddOnContentMetaExtendedHeader() const
{
return mAddOnContentMetaExtendedHeader;
}
void nx::ContentMetaBinary::setAddOnContentMetaExtendedHeader(const AddOnContentMetaExtendedHeader& exhdr)
{
mAddOnContentMetaExtendedHeader = exhdr;
}
const nx::ContentMetaBinary::DeltaMetaExtendedHeader& nx::ContentMetaBinary::getDeltaMetaExtendedHeader() const
{
return mDeltaMetaExtendedHeader;
}
void nx::ContentMetaBinary::setDeltaMetaExtendedHeader(const DeltaMetaExtendedHeader& exhdr)
{
mDeltaMetaExtendedHeader = exhdr;
} }
const fnd::List<nx::ContentMetaBinary::ContentInfo>& nx::ContentMetaBinary::getContentInfo() const const fnd::List<nx::ContentMetaBinary::ContentInfo>& nx::ContentMetaBinary::getContentInfo() const
@ -198,34 +263,43 @@ const nx::sDigest & nx::ContentMetaBinary::getDigest() const
void nx::ContentMetaBinary::setDigest(const nx::sDigest & digest) void nx::ContentMetaBinary::setDigest(const nx::sDigest & digest)
{ {
memcpy(mDigest.data, digest.data, cmnt::kDigestLen); memcpy(mDigest.data, digest.data, cnmt::kDigestLen);
} }
bool nx::ContentMetaBinary::validateExtendedHeaderSize(cmnt::ContentMetaType type, size_t exhdrSize) bool nx::ContentMetaBinary::validateExtendedHeaderSize(cnmt::ContentMetaType type, size_t exhdrSize) const
{ {
bool validSize = false; bool validSize = false;
if (type == cmnt::METATYPE_APPLICATION && exhdrSize == sizeof(sApplicationMetaExtendedHeader)) switch (type)
validSize = true; {
else if (type == cmnt::METATYPE_PATCH && exhdrSize == sizeof(sPatchMetaExtendedHeader)) case (cnmt::METATYPE_APPLICATION):
validSize = true; validSize = (exhdrSize == sizeof(sApplicationMetaExtendedHeader));
else if (type == cmnt::METATYPE_ADD_ON_CONTENT && exhdrSize == sizeof(sAddOnContentMetaExtendedHeader)) break;
validSize = true; case (cnmt::METATYPE_PATCH):
else if (type == cmnt::METATYPE_DELTA && exhdrSize == sizeof(sDeltaMetaExtendedHeader)) validSize = (exhdrSize == sizeof(sPatchMetaExtendedHeader));
validSize = true; break;
case (cnmt::METATYPE_ADD_ON_CONTENT):
validSize = (exhdrSize == sizeof(sAddOnContentMetaExtendedHeader));
break;
case (cnmt::METATYPE_DELTA):
validSize = (exhdrSize == sizeof(sDeltaMetaExtendedHeader));
break;
default:
validSize = (exhdrSize == 0);
}
return validSize; return validSize;
} }
size_t nx::ContentMetaBinary::getExtendedDataSize(cmnt::ContentMetaType type, const byte_t * data) size_t nx::ContentMetaBinary::getExtendedDataSize(cnmt::ContentMetaType type, const byte_t * data) const
{ {
size_t exdata_len = 0; size_t exdata_len = 0;
if (type == cmnt::METATYPE_PATCH) if (type == cnmt::METATYPE_PATCH)
{ {
const sPatchMetaExtendedHeader* exhdr = (const sPatchMetaExtendedHeader*)(data); const sPatchMetaExtendedHeader* exhdr = (const sPatchMetaExtendedHeader*)(data);
exdata_len = exhdr->extended_data_size.get(); exdata_len = exhdr->extended_data_size.get();
} }
else if (type == cmnt::METATYPE_DELTA) else if (type == cnmt::METATYPE_DELTA)
{ {
const sDeltaMetaExtendedHeader* exhdr = (const sDeltaMetaExtendedHeader*)(data); const sDeltaMetaExtendedHeader* exhdr = (const sDeltaMetaExtendedHeader*)(data);
exdata_len = exhdr->extended_data_size.get(); exdata_len = exhdr->extended_data_size.get();
@ -233,7 +307,7 @@ size_t nx::ContentMetaBinary::getExtendedDataSize(cmnt::ContentMetaType type, co
return exdata_len; return exdata_len;
} }
void nx::ContentMetaBinary::validateBinary(const byte_t * bytes, size_t len) void nx::ContentMetaBinary::validateBinary(const byte_t * bytes, size_t len) const
{ {
// check if it is large enough to read the header // check if it is large enough to read the header
if (len < sizeof(sContentMetaHeader)) if (len < sizeof(sContentMetaHeader))
@ -245,7 +319,7 @@ void nx::ContentMetaBinary::validateBinary(const byte_t * bytes, size_t len)
const sContentMetaHeader* hdr = (const sContentMetaHeader*)bytes; const sContentMetaHeader* hdr = (const sContentMetaHeader*)bytes;
// validate extended header size // validate extended header size
if (validateExtendedHeaderSize((cmnt::ContentMetaType)hdr->type, hdr->exhdr_size.get()) == false) if (validateExtendedHeaderSize((cnmt::ContentMetaType)hdr->type, hdr->exhdr_size.get()) == false)
{ {
throw fnd::Exception(kModuleName, "Invalid extended header size"); throw fnd::Exception(kModuleName, "Invalid extended header size");
} }
@ -257,7 +331,7 @@ void nx::ContentMetaBinary::validateBinary(const byte_t * bytes, size_t len)
} }
// check binary size again with extended data size // 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()))) if (len < getTotalSize(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), getExtendedDataSize((cnmt::ContentMetaType)hdr->type, bytes + getExtendedHeaderOffset())))
{ {
throw fnd::Exception(kModuleName, "Binary too small"); throw fnd::Exception(kModuleName, "Binary too small");
} }
@ -269,12 +343,16 @@ bool nx::ContentMetaBinary::isEqual(const ContentMetaBinary & other) const
&& (mTitleVersion == other.mTitleVersion) \ && (mTitleVersion == other.mTitleVersion) \
&& (mType == other.mType) \ && (mType == other.mType) \
&& (mAttributes == other.mAttributes) \ && (mAttributes == other.mAttributes) \
&& (mRequiredSystemVersion == other.mRequiredSystemVersion) \ && (mRequiredDownloadSystemVersion == other.mRequiredDownloadSystemVersion) \
&& (mExtendedHeader == other.mExtendedHeader) \ && (mExtendedHeader == other.mExtendedHeader) \
&& (mApplicationMetaExtendedHeader == other.mApplicationMetaExtendedHeader) \
&& (mPatchMetaExtendedHeader == other.mPatchMetaExtendedHeader) \
&& (mAddOnContentMetaExtendedHeader == other.mAddOnContentMetaExtendedHeader) \
&& (mDeltaMetaExtendedHeader == other.mDeltaMetaExtendedHeader) \
&& (mContentInfo == other.mContentInfo) \ && (mContentInfo == other.mContentInfo) \
&& (mContentMetaInfo == other.mContentMetaInfo) \ && (mContentMetaInfo == other.mContentMetaInfo) \
&& (mExtendedData == other.mExtendedData) \ && (mExtendedData == other.mExtendedData) \
&& (memcmp(mDigest.data, other.mDigest.data, cmnt::kDigestLen) == 0); && (memcmp(mDigest.data, other.mDigest.data, cnmt::kDigestLen) == 0);
} }
void nx::ContentMetaBinary::copyFrom(const ContentMetaBinary & other) void nx::ContentMetaBinary::copyFrom(const ContentMetaBinary & other)
@ -290,11 +368,15 @@ void nx::ContentMetaBinary::copyFrom(const ContentMetaBinary & other)
mTitleVersion = other.mTitleVersion; mTitleVersion = other.mTitleVersion;
mType = other.mType; mType = other.mType;
mAttributes = other.mAttributes; mAttributes = other.mAttributes;
mRequiredSystemVersion = other.mRequiredSystemVersion; mRequiredDownloadSystemVersion = other.mRequiredDownloadSystemVersion;
mExtendedHeader = other.mExtendedHeader; mExtendedHeader = other.mExtendedHeader;
mApplicationMetaExtendedHeader = other.mApplicationMetaExtendedHeader;
mPatchMetaExtendedHeader = other.mPatchMetaExtendedHeader;
mAddOnContentMetaExtendedHeader = other.mAddOnContentMetaExtendedHeader;
mDeltaMetaExtendedHeader = other.mDeltaMetaExtendedHeader;
mContentInfo = other.mContentInfo; mContentInfo = other.mContentInfo;
mContentMetaInfo = other.mContentMetaInfo; mContentMetaInfo = other.mContentMetaInfo;
mExtendedData = other.mExtendedData; mExtendedData = other.mExtendedData;
memcpy(mDigest.data, other.mDigest.data, cmnt::kDigestLen); memcpy(mDigest.data, other.mDigest.data, cnmt::kDigestLen);
} }
} }

View file

@ -1 +0,0 @@
#include "CmntProcess.h"

View file

@ -1 +0,0 @@
#pragma once

View file

@ -0,0 +1,185 @@
#include <fnd/SimpleTextOutput.h>
#include "OffsetAdjustedIFile.h"
#include "CnmtProcess.h"
const std::string kContentTypeStr[7] =
{
"Meta",
"Program",
"Data",
"Control",
"HtmlDocument",
"LegalInformation",
"DeltaFragment"
};
const std::string kContentMetaTypeStr[2][0x80] =
{
{
""
"SystemProgram",
"SystemData",
"SystemUpdate",
"BootImagePackage",
"BootImagePackageSafe"
},
{
"Application",
"Patch",
"AddOnContent",
"Delta"
}
};
inline const std::string& getContentMetaTypeStr(byte_t index)
{
return (index < 0x80) ? kContentMetaTypeStr[0][index] : kContentMetaTypeStr[1][index-0x80];
}
const std::string kUpdateTypeStr[3] =
{
"ApplyAsDelta",
"Overwrite",
"Create"
};
const std::string kContentMetaAttrStr[3] =
{
"IncludesExFatDriver",
"Rebootless"
};
inline const char* getBoolStr(bool isTrue)
{
return isTrue? "TRUE" : "FALSE";
}
void CnmtProcess::displayCmnt()
{
#define _SPLIT_VER(ver) ( (ver>>24) & 0xff), ( (ver>>16) & 0xff), ( (ver>>8) & 0xff), (ver & 0xff)
printf("[ContentMeta]\n");
printf(" TitleId: 0x%" PRIx64 "\n", mCnmt.getTitleId());
uint32_t ver = mCnmt.getTitleVersion();
printf(" Version: v%d.%d.%d-%d (v%" PRId32 ")\n", _SPLIT_VER(ver), ver);
printf(" Type: %s\n", getContentMetaTypeStr(mCnmt.getType()).c_str());
printf(" Attributes: %x\n", mCnmt.getAttributes());
printf(" IncludesExFatDriver: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_INCLUDES_EX_FAT_DRIVER)));
printf(" Rebootless: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_REBOOTLESS)));
ver = mCnmt.getRequiredDownloadSystemVersion();
printf(" RequiredDownloadSystemVersion: v%d.%d.%d-%d (v%" PRId32 ")\n", _SPLIT_VER(ver), ver);
switch(mCnmt.getType())
{
case (nx::cnmt::METATYPE_APPLICATION):
printf(" ApplicationExtendedHeader:\n");
printf(" RequiredSystemVersion: %" PRId32 "\n", mCnmt.getApplicationMetaExtendedHeader().required_system_version);
printf(" PatchId: 0x%016" PRIx64 "\n", mCnmt.getApplicationMetaExtendedHeader().patch_id);
break;
case (nx::cnmt::METATYPE_PATCH):
printf(" PatchMetaExtendedHeader:\n");
printf(" RequiredSystemVersion: %" PRId32 "\n", mCnmt.getPatchMetaExtendedHeader().required_system_version);
printf(" ApplicationId: 0x%016" PRIx64 "\n", mCnmt.getPatchMetaExtendedHeader().application_id);
break;
case (nx::cnmt::METATYPE_ADD_ON_CONTENT):
printf(" AddOnContentMetaExtendedHeader:\n");
printf(" RequiredSystemVersion: %" PRId32 "\n", mCnmt.getAddOnContentMetaExtendedHeader().required_system_version);
printf(" ApplicationId: 0x%016" PRIx64 "\n", mCnmt.getAddOnContentMetaExtendedHeader().application_id);
break;
case (nx::cnmt::METATYPE_DELTA):
printf(" DeltaMetaExtendedHeader:\n");
printf(" ApplicationId: 0x%016" PRIx64 "\n", mCnmt.getDeltaMetaExtendedHeader().application_id);
break;
default:
break;
}
if (mCnmt.getContentInfo().getSize() > 0)
{
printf(" ContentInfo:\n");
for (size_t i = 0; i < mCnmt.getContentInfo().getSize(); i++)
{
const nx::ContentMetaBinary::ContentInfo& info = mCnmt.getContentInfo()[i];
printf(" %d\n", i);
printf(" Type: %s\n", kContentTypeStr[info.type].c_str());
printf(" Id: ");
fnd::SimpleTextOutput::hexDump(info.nca_id, nx::cnmt::kContentIdLen);
printf(" Size: 0x%" PRIx64 "\n", info.size);
printf(" Hash: ");
fnd::SimpleTextOutput::hexDump(info.hash.bytes, sizeof(info.hash));
}
}
if (mCnmt.getContentMetaInfo().getSize() > 0)
{
printf(" ContentMetaInfo:\n");
for (size_t i = 0; i < mCnmt.getContentMetaInfo().getSize(); i++)
{
const nx::ContentMetaBinary::ContentMetaInfo& info = mCnmt.getContentMetaInfo()[i];
printf(" %d\n", i);
printf(" Id: 0x%" PRIx64 "\n", info.id);
ver = info.version;
printf(" Version: v%d.%d.%d-%d (v%" PRId32 ")\n", _SPLIT_VER(ver), ver);
printf(" Type: %s\n", getContentMetaTypeStr(info.type).c_str());
printf(" Attributes: %x\n", mCnmt.getAttributes());
printf(" IncludesExFatDriver: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_INCLUDES_EX_FAT_DRIVER)));
printf(" Rebootless: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_REBOOTLESS)));
}
}
printf(" Digest: ");
fnd::SimpleTextOutput::hexDump(mCnmt.getDigest().data, nx::cnmt::kDigestLen);
#undef _SPLIT_VER
}
CnmtProcess::CnmtProcess() :
mReader(nullptr),
mCliOutputType(OUTPUT_NORMAL),
mVerify(false)
{
}
CnmtProcess::~CnmtProcess()
{
if (mReader != nullptr)
{
delete mReader;
}
}
void CnmtProcess::process()
{
fnd::MemoryBlob scratch;
if (mReader == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
}
scratch.alloc(mReader->size());
mReader->read(scratch.getBytes(), 0, scratch.getSize());
mCnmt.importBinary(scratch.getBytes(), scratch.getSize());
if (mCliOutputType >= OUTPUT_NORMAL)
{
displayCmnt();
}
}
void CnmtProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size)
{
mReader = new OffsetAdjustedIFile(file, offset, size);
}
void CnmtProcess::setCliOutputMode(CliOutputType type)
{
mCliOutputType = type;
}
void CnmtProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
const nx::ContentMetaBinary& CnmtProcess::getContentMetaBinary() const
{
return mCnmt;
}

View file

@ -0,0 +1,33 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <nx/ContentMetaBinary.h>
#include "nstool.h"
class CnmtProcess
{
public:
CnmtProcess();
~CnmtProcess();
void process();
void setInputFile(fnd::IFile* file, size_t offset, size_t size);
void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify);
const nx::ContentMetaBinary& getContentMetaBinary() const;
private:
const std::string kModuleName = "CnmtProcess";
fnd::IFile* mReader;
CliOutputType mCliOutputType;
bool mVerify;
nx::ContentMetaBinary mCnmt;
void displayCmnt();
};

View file

@ -2,10 +2,10 @@
#include <fnd/SimpleTextOutput.h> #include <fnd/SimpleTextOutput.h>
#include <nx/NcaUtils.h> #include <nx/NcaUtils.h>
#include <nx/AesKeygen.h> #include <nx/AesKeygen.h>
#include <nx/NpdmBinary.h>
#include "NcaProcess.h" #include "NcaProcess.h"
#include "PfsProcess.h" #include "PfsProcess.h"
#include "RomfsProcess.h" #include "RomfsProcess.h"
#include "NpdmProcess.h"
#include "OffsetAdjustedIFile.h" #include "OffsetAdjustedIFile.h"
#include "AesCtrWrappedIFile.h" #include "AesCtrWrappedIFile.h"
#include "CopiedIFile.h" #include "CopiedIFile.h"
@ -199,10 +199,11 @@ void NcaProcess::generatePartitionConfiguration()
throw fnd::Exception(kModuleName, error.str()); throw fnd::Exception(kModuleName, error.str());
} }
// determine the data offset // determine the data offset & size
if (mPartitions[partition.index].hash_type == nx::nca::HASH_HIERARCHICAL_SHA256) if (mPartitions[partition.index].hash_type == nx::nca::HASH_HIERARCHICAL_SHA256)
{ {
mPartitions[partition.index].data_offset = mPartitions[partition.index].hierarchicalsha256_header.hash_target.offset.get(); mPartitions[partition.index].data_offset = mPartitions[partition.index].hierarchicalsha256_header.hash_target.offset.get();
mPartitions[partition.index].data_size = mPartitions[partition.index].hierarchicalsha256_header.hash_target.size.get();
} }
else if (mPartitions[partition.index].hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) else if (mPartitions[partition.index].hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY)
{ {
@ -211,6 +212,7 @@ void NcaProcess::generatePartitionConfiguration()
if (mPartitions[partition.index].ivfc_header.level_header[nx::ivfc::kMaxIvfcLevel-1-j].logical_offset.get() != 0) if (mPartitions[partition.index].ivfc_header.level_header[nx::ivfc::kMaxIvfcLevel-1-j].logical_offset.get() != 0)
{ {
mPartitions[partition.index].data_offset = mPartitions[partition.index].ivfc_header.level_header[nx::ivfc::kMaxIvfcLevel-1-j].logical_offset.get(); mPartitions[partition.index].data_offset = mPartitions[partition.index].ivfc_header.level_header[nx::ivfc::kMaxIvfcLevel-1-j].logical_offset.get();
mPartitions[partition.index].data_size = mPartitions[partition.index].ivfc_header.level_header[nx::ivfc::kMaxIvfcLevel-1-j].hash_data_size.get();
break; break;
} }
} }
@ -245,25 +247,21 @@ void NcaProcess::validateNcaSignatures()
if (mPartitions[0].reader != nullptr) if (mPartitions[0].reader != nullptr)
{ {
PfsProcess exefs; PfsProcess exefs;
exefs.setInputFile(mPartitions[0].reader); exefs.setInputFile(mPartitions[0].reader, mPartitions[0].offset + mPartitions[0].data_offset, mPartitions[0].data_size);
exefs.setInputFileOffset(mPartitions[0].offset + mPartitions[0].data_offset);
exefs.setCliOutputMode(OUTPUT_MINIMAL); exefs.setCliOutputMode(OUTPUT_MINIMAL);
exefs.process(); exefs.process();
// open main.npdm // open main.npdm
if (exefs.getPfsHeader().getFileList().hasElement(kNpdmExefsPath) == true) if (exefs.getPfsHeader().getFileList().hasElement(kNpdmExefsPath) == true)
{ {
const nx::PfsHeader::sFile& npdmFile = exefs.getPfsHeader().getFileList()[exefs.getPfsHeader().getFileList().getIndexOf(kNpdmExefsPath)]; const nx::PfsHeader::sFile& file = exefs.getPfsHeader().getFileList()[exefs.getPfsHeader().getFileList().getIndexOf(kNpdmExefsPath)];
fnd::MemoryBlob scratch; NpdmProcess npdm;
scratch.alloc(npdmFile.size); npdm.setInputFile(mPartitions[0].reader, mPartitions[0].offset + mPartitions[0].data_offset + file.offset, file.size);
mPartitions[0].reader->read(scratch.getBytes(), mPartitions[0].offset + mPartitions[0].data_offset + npdmFile.offset, npdmFile.size); npdm.setCliOutputMode(OUTPUT_MINIMAL);
npdm.process();
nx::NpdmBinary npdmBinary; if (crypto::rsa::pss::rsaVerify(npdm.getNpdmBinary().getAcid().getNcaHeader2RsaKey(), crypto::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_acid) != 0)
npdmBinary.importBinary(scratch.getBytes(), scratch.getSize());
if (crypto::rsa::pss::rsaVerify(npdmBinary.getAcid().getNcaHeader2RsaKey(), crypto::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_acid) != 0)
{ {
// this is minimal even though it's a warning because it's a validation method // this is minimal even though it's a warning because it's a validation method
if (mCliOutputType >= OUTPUT_MINIMAL) if (mCliOutputType >= OUTPUT_MINIMAL)
@ -429,8 +427,7 @@ void NcaProcess::processPartitions()
if (partition.format_type == nx::nca::FORMAT_PFS0) if (partition.format_type == nx::nca::FORMAT_PFS0)
{ {
PfsProcess pfs; PfsProcess pfs;
pfs.setInputFile(partition.reader); pfs.setInputFile(partition.reader, partition.offset + partition.data_offset, partition.data_size);
pfs.setInputFileOffset(partition.offset + partition.data_offset);
pfs.setCliOutputMode(mCliOutputType); pfs.setCliOutputMode(mCliOutputType);
pfs.setListFs(mListFs); pfs.setListFs(mListFs);
if (mPartitionPath[index].doExtract) if (mPartitionPath[index].doExtract)
@ -442,8 +439,7 @@ void NcaProcess::processPartitions()
else if (partition.format_type == nx::nca::FORMAT_ROMFS) else if (partition.format_type == nx::nca::FORMAT_ROMFS)
{ {
RomfsProcess romfs; RomfsProcess romfs;
romfs.setInputFile(partition.reader); romfs.setInputFile(partition.reader, partition.offset + partition.data_offset, partition.data_size);
romfs.setInputFileOffset(partition.offset + partition.data_offset);
romfs.setCliOutputMode(mCliOutputType); romfs.setCliOutputMode(mCliOutputType);
romfs.setListFs(mListFs); romfs.setListFs(mListFs);
if (mPartitionPath[index].doExtract) if (mPartitionPath[index].doExtract)
@ -457,7 +453,6 @@ void NcaProcess::processPartitions()
NcaProcess::NcaProcess() : NcaProcess::NcaProcess() :
mReader(nullptr), mReader(nullptr),
mOffset(0),
mKeyset(nullptr), mKeyset(nullptr),
mCliOutputType(OUTPUT_NORMAL), mCliOutputType(OUTPUT_NORMAL),
mVerify(false), mVerify(false),
@ -472,6 +467,11 @@ NcaProcess::NcaProcess() :
NcaProcess::~NcaProcess() NcaProcess::~NcaProcess()
{ {
if (mReader != nullptr)
{
delete mReader;
}
for (size_t i = 0; i < nx::nca::kPartitionNum; i++) for (size_t i = 0; i < nx::nca::kPartitionNum; i++)
{ {
if (mPartitions[i].reader != nullptr) if (mPartitions[i].reader != nullptr)
@ -491,7 +491,7 @@ void NcaProcess::process()
} }
// read header block // read header block
mReader->read((byte_t*)&mHdrBlock, mOffset, sizeof(nx::sNcaHeaderBlock)); mReader->read((byte_t*)&mHdrBlock, 0, sizeof(nx::sNcaHeaderBlock));
// decrypt header block // decrypt header block
nx::NcaUtils::decryptNcaHeader((byte_t*)&mHdrBlock, (byte_t*)&mHdrBlock, mKeyset->nca.header_key); nx::NcaUtils::decryptNcaHeader((byte_t*)&mHdrBlock, (byte_t*)&mHdrBlock, mKeyset->nca.header_key);
@ -539,22 +539,11 @@ void NcaProcess::process()
so the verification text can be presented without interuption so the verification text can be presented without interuption
*/ */
// decrypt key area
} }
void NcaProcess::setInputFile(fnd::IFile* reader) void NcaProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size)
{ {
mReader = reader; mReader = new OffsetAdjustedIFile(file, offset, size);
}
void NcaProcess::setInputFileOffset(size_t offset)
{
mOffset = offset;
} }
void NcaProcess::setKeyset(const sKeyset* keyset) void NcaProcess::setKeyset(const sKeyset* keyset)

View file

@ -15,8 +15,7 @@ public:
void process(); void process();
// generic // generic
void setInputFile(fnd::IFile* reader); void setInputFile(fnd::IFile* file, size_t offset, size_t size);
void setInputFileOffset(size_t offset);
void setKeyset(const sKeyset* keyset); void setKeyset(const sKeyset* keyset);
void setCliOutputMode(CliOutputType type); void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify); void setVerifyMode(bool verify);
@ -34,7 +33,6 @@ private:
// user options // user options
fnd::IFile* mReader; fnd::IFile* mReader;
size_t mOffset;
const sKeyset* mKeyset; const sKeyset* mKeyset;
CliOutputType mCliOutputType; CliOutputType mCliOutputType;
bool mVerify; bool mVerify;
@ -63,8 +61,9 @@ private:
{ {
fnd::IFile* reader; fnd::IFile* reader;
size_t offset; size_t offset;
size_t data_offset;
size_t size; size_t size;
size_t data_offset;
size_t data_size;
nx::nca::FormatType format_type; nx::nca::FormatType format_type;
nx::nca::HashType hash_type; nx::nca::HashType hash_type;

View file

@ -1,6 +1,5 @@
#include "OffsetAdjustedIFile.h"
#include "NpdmProcess.h" #include "NpdmProcess.h"
#include <fnd/SimpleFile.h>
#include <fnd/MemoryBlob.h>
const std::string kInstructionType[2] = { "32Bit", "64Bit" }; const std::string kInstructionType[2] = { "32Bit", "64Bit" };
const std::string kProcAddrSpace[4] = { "Unknown", "64Bit", "32Bit", "32Bit no reserved" }; const std::string kProcAddrSpace[4] = { "Unknown", "64Bit", "32Bit", "32Bit no reserved" };
@ -619,13 +618,20 @@ void NpdmProcess::displayKernelCap(const nx::KcBinary& kern)
NpdmProcess::NpdmProcess() : NpdmProcess::NpdmProcess() :
mReader(nullptr), mReader(nullptr),
mOffset(0),
mKeyset(nullptr), mKeyset(nullptr),
mCliOutputType(OUTPUT_NORMAL), mCliOutputType(OUTPUT_NORMAL),
mVerify(false) mVerify(false)
{ {
} }
NpdmProcess::~NpdmProcess()
{
if (mReader != nullptr)
{
delete mReader;
}
}
void NpdmProcess::process() void NpdmProcess::process()
{ {
fnd::MemoryBlob scratch; fnd::MemoryBlob scratch;
@ -665,14 +671,9 @@ void NpdmProcess::process()
} }
} }
void NpdmProcess::setInputFile(fnd::IFile* reader) void NpdmProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size)
{ {
mReader = reader; mReader = new OffsetAdjustedIFile(file, offset, size);
}
void NpdmProcess::setInputFileOffset(size_t offset)
{
mOffset = offset;
} }
void NpdmProcess::setKeyset(const sKeyset* keyset) void NpdmProcess::setKeyset(const sKeyset* keyset)
@ -688,4 +689,9 @@ void NpdmProcess::setCliOutputMode(CliOutputType type)
void NpdmProcess::setVerifyMode(bool verify) void NpdmProcess::setVerifyMode(bool verify)
{ {
mVerify = verify; mVerify = verify;
}
const nx::NpdmBinary& NpdmProcess::getNpdmBinary() const
{
return mNpdm;
} }

View file

@ -10,20 +10,21 @@ class NpdmProcess
{ {
public: public:
NpdmProcess(); NpdmProcess();
~NpdmProcess();
void process(); void process();
void setInputFile(fnd::IFile* reader); void setInputFile(fnd::IFile* file, size_t offset, size_t size);
void setInputFileOffset(size_t offset);
void setKeyset(const sKeyset* keyset); void setKeyset(const sKeyset* keyset);
void setCliOutputMode(CliOutputType type); void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify); void setVerifyMode(bool verify);
const nx::NpdmBinary& getNpdmBinary() const;
private: private:
const std::string kModuleName = "NpdmProcess"; const std::string kModuleName = "NpdmProcess";
fnd::IFile* mReader; fnd::IFile* mReader;
size_t mOffset;
const sKeyset* mKeyset; const sKeyset* mKeyset;
CliOutputType mCliOutputType; CliOutputType mCliOutputType;
bool mVerify; bool mVerify;

View file

@ -16,7 +16,7 @@ size_t OffsetAdjustedIFile::size()
void OffsetAdjustedIFile::seek(size_t offset) void OffsetAdjustedIFile::seek(size_t offset)
{ {
mCurrentOffset = offset; mCurrentOffset = MIN(offset, mSize);
mFile->seek(offset + mBaseOffset); mFile->seek(offset + mBaseOffset);
} }

View file

@ -1,6 +1,7 @@
#include "PfsProcess.h"
#include <fnd/SimpleFile.h> #include <fnd/SimpleFile.h>
#include <fnd/io.h> #include <fnd/io.h>
#include "OffsetAdjustedIFile.h"
#include "PfsProcess.h"
void PfsProcess::displayHeader() void PfsProcess::displayHeader()
{ {
@ -55,7 +56,7 @@ void PfsProcess::validateHfs()
for (size_t i = 0; i < file.getSize(); i++) for (size_t i = 0; i < file.getSize(); i++)
{ {
scratch.alloc(file[i].hash_protected_size); scratch.alloc(file[i].hash_protected_size);
mReader->read(scratch.getBytes(), mOffset + file[i].offset, file[i].hash_protected_size); mReader->read(scratch.getBytes(), file[i].offset, file[i].hash_protected_size);
crypto::sha::Sha256(scratch.getBytes(), scratch.getSize(), hash.bytes); crypto::sha::Sha256(scratch.getBytes(), scratch.getSize(), hash.bytes);
if (hash != file[i].hash) if (hash != file[i].hash)
{ {
@ -85,7 +86,7 @@ void PfsProcess::extractFs()
fnd::io::appendToPath(file_path, mExtractPath); fnd::io::appendToPath(file_path, mExtractPath);
fnd::io::appendToPath(file_path, file[i].name); fnd::io::appendToPath(file_path, file[i].name);
outFile.open(file_path, outFile.Create); outFile.open(file_path, outFile.Create);
mReader->seek(mOffset + file[i].offset); mReader->seek(file[i].offset);
for (size_t j = 0; j < (file[i].size / kFileExportBlockSize); j++) for (size_t j = 0; j < (file[i].size / kFileExportBlockSize); j++)
{ {
mReader->read(scratch.getBytes(), kFileExportBlockSize); mReader->read(scratch.getBytes(), kFileExportBlockSize);
@ -102,8 +103,6 @@ void PfsProcess::extractFs()
PfsProcess::PfsProcess() : PfsProcess::PfsProcess() :
mReader(nullptr), mReader(nullptr),
mOffset(0),
mKeyset(nullptr),
mCliOutputType(OUTPUT_NORMAL), mCliOutputType(OUTPUT_NORMAL),
mVerify(false), mVerify(false),
mExtractPath(), mExtractPath(),
@ -112,7 +111,14 @@ PfsProcess::PfsProcess() :
mListFs(false), mListFs(false),
mPfs() mPfs()
{ {
}
PfsProcess::~PfsProcess()
{
if (mReader != nullptr)
{
delete mReader;
}
} }
void PfsProcess::process() void PfsProcess::process()
@ -126,7 +132,7 @@ void PfsProcess::process()
// open minimum header to get full header size // open minimum header to get full header size
scratch.alloc(sizeof(nx::sPfsHeader)); scratch.alloc(sizeof(nx::sPfsHeader));
mReader->read(scratch.getBytes(), mOffset, scratch.getSize()); mReader->read(scratch.getBytes(), 0, scratch.getSize());
if (validateHeaderMagic(((nx::sPfsHeader*)scratch.getBytes())) == false) if (validateHeaderMagic(((nx::sPfsHeader*)scratch.getBytes())) == false)
{ {
throw fnd::Exception(kModuleName, "Corrupt Header"); throw fnd::Exception(kModuleName, "Corrupt Header");
@ -135,7 +141,7 @@ void PfsProcess::process()
// open minimum header to get full header size // open minimum header to get full header size
scratch.alloc(pfsHeaderSize); scratch.alloc(pfsHeaderSize);
mReader->read(scratch.getBytes(), mOffset, scratch.getSize()); mReader->read(scratch.getBytes(), 0, scratch.getSize());
mPfs.importBinary(scratch.getBytes(), scratch.getSize()); mPfs.importBinary(scratch.getBytes(), scratch.getSize());
if (mCliOutputType >= OUTPUT_NORMAL) if (mCliOutputType >= OUTPUT_NORMAL)
@ -148,19 +154,9 @@ void PfsProcess::process()
extractFs(); extractFs();
} }
void PfsProcess::setInputFile(fnd::IFile* reader) void PfsProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size)
{ {
mReader = reader; mReader = new OffsetAdjustedIFile(file, offset, size);
}
void PfsProcess::setInputFileOffset(size_t offset)
{
mOffset = offset;
}
void PfsProcess::setKeyset(const sKeyset* keyset)
{
mKeyset = keyset;
} }
void PfsProcess::setCliOutputMode(CliOutputType type) void PfsProcess::setCliOutputMode(CliOutputType type)

View file

@ -10,13 +10,12 @@ class PfsProcess
{ {
public: public:
PfsProcess(); PfsProcess();
~PfsProcess();
void process(); void process();
// generic // generic
void setInputFile(fnd::IFile* reader); void setInputFile(fnd::IFile* file, size_t offset, size_t size);
void setInputFileOffset(size_t offset);
void setKeyset(const sKeyset* keyset);
void setCliOutputMode(CliOutputType type); void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify); void setVerifyMode(bool verify);
@ -32,8 +31,6 @@ private:
static const size_t kFileExportBlockSize = 0x1000000; static const size_t kFileExportBlockSize = 0x1000000;
fnd::IFile* mReader; fnd::IFile* mReader;
size_t mOffset;
const sKeyset* mKeyset;
CliOutputType mCliOutputType; CliOutputType mCliOutputType;
bool mVerify; bool mVerify;

View file

@ -1,7 +1,8 @@
#include "RomfsProcess.h"
#include <fnd/SimpleTextOutput.h> #include <fnd/SimpleTextOutput.h>
#include <fnd/SimpleFile.h> #include <fnd/SimpleFile.h>
#include <fnd/io.h> #include <fnd/io.h>
#include "OffsetAdjustedIFile.h"
#include "RomfsProcess.h"
void RomfsProcess::printTab(size_t tab) const void RomfsProcess::printTab(size_t tab) const
{ {
@ -87,7 +88,7 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir)
outFile.open(file_path, outFile.Create); outFile.open(file_path, outFile.Create);
mReader->seek(mOffset + dir.file_list[i].offset); mReader->seek(dir.file_list[i].offset);
for (size_t j = 0; j < (dir.file_list[i].size / kFileExportBlockSize); j++) for (size_t j = 0; j < (dir.file_list[i].size / kFileExportBlockSize); j++)
{ {
mReader->read(scratch.getBytes(), kFileExportBlockSize); mReader->read(scratch.getBytes(), kFileExportBlockSize);
@ -186,7 +187,7 @@ void RomfsProcess::importDirectory(uint32_t dir_offset, sDirectory& dir)
void RomfsProcess::resolveRomfs() void RomfsProcess::resolveRomfs()
{ {
// read header // read header
mReader->read((byte_t*)&mHdr, mOffset, sizeof(nx::sRomfsHeader)); mReader->read((byte_t*)&mHdr, 0, sizeof(nx::sRomfsHeader));
// logic check on the header layout // logic check on the header layout
if (validateHeaderLayout(&mHdr) == false) if (validateHeaderLayout(&mHdr) == false)
@ -196,13 +197,13 @@ void RomfsProcess::resolveRomfs()
// read directory nodes // read directory nodes
mDirNodes.alloc(mHdr.sections[nx::romfs::DIR_NODE_TABLE].size.get()); mDirNodes.alloc(mHdr.sections[nx::romfs::DIR_NODE_TABLE].size.get());
mReader->read(mDirNodes.getBytes(), mOffset + mHdr.sections[nx::romfs::DIR_NODE_TABLE].offset.get(), mDirNodes.getSize()); mReader->read(mDirNodes.getBytes(), mHdr.sections[nx::romfs::DIR_NODE_TABLE].offset.get(), mDirNodes.getSize());
//printf("[RAW DIR NODES]\n"); //printf("[RAW DIR NODES]\n");
//fnd::SimpleTextOutput::hxdStyleDump(mDirNodes.getBytes(), mDirNodes.getSize()); //fnd::SimpleTextOutput::hxdStyleDump(mDirNodes.getBytes(), mDirNodes.getSize());
// read file nodes // read file nodes
mFileNodes.alloc(mHdr.sections[nx::romfs::FILE_NODE_TABLE].size.get()); mFileNodes.alloc(mHdr.sections[nx::romfs::FILE_NODE_TABLE].size.get());
mReader->read(mFileNodes.getBytes(), mOffset + mHdr.sections[nx::romfs::FILE_NODE_TABLE].offset.get(), mFileNodes.getSize()); mReader->read(mFileNodes.getBytes(), mHdr.sections[nx::romfs::FILE_NODE_TABLE].offset.get(), mFileNodes.getSize());
//printf("[RAW FILE NODES]\n"); //printf("[RAW FILE NODES]\n");
//fnd::SimpleTextOutput::hxdStyleDump(mFileNodes.getBytes(), mFileNodes.getSize()); //fnd::SimpleTextOutput::hxdStyleDump(mFileNodes.getBytes(), mFileNodes.getSize());
@ -223,8 +224,6 @@ void RomfsProcess::resolveRomfs()
RomfsProcess::RomfsProcess() : RomfsProcess::RomfsProcess() :
mReader(nullptr), mReader(nullptr),
mOffset(0),
mKeyset(nullptr),
mCliOutputType(OUTPUT_NORMAL), mCliOutputType(OUTPUT_NORMAL),
mVerify(false), mVerify(false),
mExtractPath(), mExtractPath(),
@ -239,6 +238,14 @@ RomfsProcess::RomfsProcess() :
mRootDir.file_list.clear(); mRootDir.file_list.clear();
} }
RomfsProcess::~RomfsProcess()
{
if (mReader != nullptr)
{
delete mReader;
}
}
void RomfsProcess::process() void RomfsProcess::process()
{ {
if (mReader == nullptr) if (mReader == nullptr)
@ -256,19 +263,9 @@ void RomfsProcess::process()
extractFs(); extractFs();
} }
void RomfsProcess::setInputFile(fnd::IFile* reader) void RomfsProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size)
{ {
mReader = reader; mReader = new OffsetAdjustedIFile(file, offset, size);
}
void RomfsProcess::setInputFileOffset(size_t offset)
{
mOffset = offset;
}
void RomfsProcess::setKeyset(const sKeyset* keyset)
{
mKeyset = keyset;
} }
void RomfsProcess::setCliOutputMode(CliOutputType type) void RomfsProcess::setCliOutputMode(CliOutputType type)

View file

@ -89,13 +89,12 @@ public:
}; };
RomfsProcess(); RomfsProcess();
~RomfsProcess();
void process(); void process();
// generic // generic
void setInputFile(fnd::IFile* reader); void setInputFile(fnd::IFile* file, size_t offset, size_t size);
void setInputFileOffset(size_t offset);
void setKeyset(const sKeyset* keyset);
void setCliOutputMode(CliOutputType type); void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify); void setVerifyMode(bool verify);
@ -110,8 +109,6 @@ private:
static const size_t kFileExportBlockSize = 0x1000000; static const size_t kFileExportBlockSize = 0x1000000;
fnd::IFile* mReader; fnd::IFile* mReader;
size_t mOffset;
const sKeyset* mKeyset;
CliOutputType mCliOutputType; CliOutputType mCliOutputType;
bool mVerify; bool mVerify;

View file

@ -38,7 +38,7 @@ void UserSettings::showHelp()
printf("\n General Options:\n"); printf("\n General Options:\n");
printf(" -d, --dev Use devkit keyset\n"); printf(" -d, --dev Use devkit keyset\n");
printf(" -k, --keyset Specify keyset file\n"); printf(" -k, --keyset Specify keyset file\n");
printf(" -t, --type Specify input file type [xci, pfs, romfs, nca, npdm, cmnt]\n"); printf(" -t, --type Specify input file type [xci, pfs, romfs, nca, npdm, cnmt]\n");
printf(" -y, --verify Verify file\n"); printf(" -y, --verify Verify file\n");
printf(" -v, --verbose Verbose output\n"); printf(" -v, --verbose Verbose output\n");
printf(" -q, --quiet Minimal output\n"); printf(" -q, --quiet Minimal output\n");
@ -576,8 +576,8 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str)
type = FILE_NCA; type = FILE_NCA;
else if (str == "npdm") else if (str == "npdm")
type = FILE_NPDM; type = FILE_NPDM;
else if (str == "cmnt") else if (str == "cnmt")
type = FILE_CMNT; type = FILE_CNMT;
else else
type = FILE_INVALID; type = FILE_INVALID;

View file

@ -1,6 +1,7 @@
#include "XciProcess.h"
#include <fnd/SimpleTextOutput.h> #include <fnd/SimpleTextOutput.h>
#include <nx/XciUtils.h> #include <nx/XciUtils.h>
#include "OffsetAdjustedIFile.h"
#include "XciProcess.h"
inline const char* getBoolStr(bool isTrue) inline const char* getBoolStr(bool isTrue)
{ {
@ -136,13 +137,12 @@ void XciProcess::processRootPfs()
{ {
if (mVerify) if (mVerify)
{ {
if (validateRegionOfFile(mOffset + mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize(), mHdr.getPartitionFsHash().bytes) == false) if (validateRegionOfFile(mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize(), mHdr.getPartitionFsHash().bytes) == false)
{ {
printf("[WARNING] XCI Root HFS0: FAIL (bad hash)\n"); printf("[WARNING] XCI Root HFS0: FAIL (bad hash)\n");
} }
} }
mRootPfs.setInputFile(mReader); mRootPfs.setInputFile(mReader, mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize());
mRootPfs.setInputFileOffset(mOffset + mHdr.getPartitionFsAddress());
mRootPfs.setListFs(mListFs); mRootPfs.setListFs(mListFs);
mRootPfs.setVerifyMode(mVerify); mRootPfs.setVerifyMode(mVerify);
mRootPfs.setCliOutputMode(mCliOutputType); mRootPfs.setCliOutputMode(mCliOutputType);
@ -156,8 +156,7 @@ void XciProcess::processPartitionPfs()
for (size_t i = 0; i < rootPartitions.getSize(); i++) for (size_t i = 0; i < rootPartitions.getSize(); i++)
{ {
PfsProcess tmp; PfsProcess tmp;
tmp.setInputFile(mReader); tmp.setInputFile(mReader, mHdr.getPartitionFsAddress() + rootPartitions[i].offset, rootPartitions[i].size);
tmp.setInputFileOffset(mOffset + mHdr.getPartitionFsAddress() + rootPartitions[i].offset);
tmp.setListFs(mListFs); tmp.setListFs(mListFs);
tmp.setVerifyMode(mVerify); tmp.setVerifyMode(mVerify);
tmp.setCliOutputMode(mCliOutputType); tmp.setCliOutputMode(mCliOutputType);
@ -174,7 +173,6 @@ void XciProcess::processPartitionPfs()
XciProcess::XciProcess() : XciProcess::XciProcess() :
mReader(nullptr), mReader(nullptr),
mOffset(0),
mKeyset(nullptr), mKeyset(nullptr),
mCliOutputType(OUTPUT_NORMAL), mCliOutputType(OUTPUT_NORMAL),
mVerify(false), mVerify(false),
@ -189,6 +187,14 @@ XciProcess::XciProcess() :
mSecurePath.doExtract = false; mSecurePath.doExtract = false;
} }
XciProcess::~XciProcess()
{
if (mReader != nullptr)
{
delete mReader;
}
}
void XciProcess::process() void XciProcess::process()
{ {
fnd::MemoryBlob scratch; fnd::MemoryBlob scratch;
@ -199,7 +205,7 @@ void XciProcess::process()
} }
// read header page // read header page
mReader->read((byte_t*)&mHdrPage, mOffset, sizeof(nx::sXciHeaderPage)); mReader->read((byte_t*)&mHdrPage, 0, sizeof(nx::sXciHeaderPage));
// allocate memory for and decrypt sXciHeader // allocate memory for and decrypt sXciHeader
scratch.alloc(sizeof(nx::sXciHeader)); scratch.alloc(sizeof(nx::sXciHeader));
@ -227,14 +233,9 @@ void XciProcess::process()
processPartitionPfs(); processPartitionPfs();
} }
void XciProcess::setInputFile(fnd::IFile* reader) void XciProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size)
{ {
mReader = reader; mReader = new OffsetAdjustedIFile(file, offset, size);
}
void XciProcess::setInputFileOffset(size_t offset)
{
mOffset = offset;
} }
void XciProcess::setKeyset(const sKeyset* keyset) void XciProcess::setKeyset(const sKeyset* keyset)

View file

@ -13,12 +13,12 @@ class XciProcess
{ {
public: public:
XciProcess(); XciProcess();
~XciProcess();
void process(); void process();
// generic // generic
void setInputFile(fnd::IFile* reader); void setInputFile(fnd::IFile* file, size_t offset, size_t size);
void setInputFileOffset(size_t offset);
void setKeyset(const sKeyset* keyset); void setKeyset(const sKeyset* keyset);
void setCliOutputMode(CliOutputType type); void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify); void setVerifyMode(bool verify);
@ -35,7 +35,6 @@ private:
static const size_t kFileExportBlockSize = 0x1000000; static const size_t kFileExportBlockSize = 0x1000000;
fnd::IFile* mReader; fnd::IFile* mReader;
size_t mOffset;
const sKeyset* mKeyset; const sKeyset* mKeyset;
CliOutputType mCliOutputType; CliOutputType mCliOutputType;
bool mVerify; bool mVerify;

View file

@ -6,6 +6,7 @@
#include "RomfsProcess.h" #include "RomfsProcess.h"
#include "NcaProcess.h" #include "NcaProcess.h"
#include "NpdmProcess.h" #include "NpdmProcess.h"
#include "CnmtProcess.h"
int main(int argc, char** argv) int main(int argc, char** argv)
@ -21,7 +22,7 @@ int main(int argc, char** argv)
{ {
XciProcess xci; XciProcess xci;
xci.setInputFile(&inputFile); xci.setInputFile(&inputFile, 0, inputFile.size());
xci.setKeyset(&user_set.getKeyset()); xci.setKeyset(&user_set.getKeyset());
xci.setCliOutputMode(user_set.getCliOutputType()); xci.setCliOutputMode(user_set.getCliOutputType());
@ -41,8 +42,7 @@ int main(int argc, char** argv)
{ {
PfsProcess pfs; PfsProcess pfs;
pfs.setInputFile(&inputFile); pfs.setInputFile(&inputFile, 0, inputFile.size());
pfs.setKeyset(&user_set.getKeyset());
pfs.setCliOutputMode(user_set.getCliOutputType()); pfs.setCliOutputMode(user_set.getCliOutputType());
pfs.setVerifyMode(user_set.isVerifyFile()); pfs.setVerifyMode(user_set.isVerifyFile());
@ -56,8 +56,7 @@ int main(int argc, char** argv)
{ {
RomfsProcess romfs; RomfsProcess romfs;
romfs.setInputFile(&inputFile); romfs.setInputFile(&inputFile, 0, inputFile.size());
romfs.setKeyset(&user_set.getKeyset());
romfs.setCliOutputMode(user_set.getCliOutputType()); romfs.setCliOutputMode(user_set.getCliOutputType());
romfs.setVerifyMode(user_set.isVerifyFile()); romfs.setVerifyMode(user_set.isVerifyFile());
@ -71,7 +70,7 @@ int main(int argc, char** argv)
{ {
NcaProcess nca; NcaProcess nca;
nca.setInputFile(&inputFile); nca.setInputFile(&inputFile, 0, inputFile.size());
nca.setKeyset(&user_set.getKeyset()); nca.setKeyset(&user_set.getKeyset());
nca.setCliOutputMode(user_set.getCliOutputType()); nca.setCliOutputMode(user_set.getCliOutputType());
nca.setVerifyMode(user_set.isVerifyFile()); nca.setVerifyMode(user_set.isVerifyFile());
@ -93,13 +92,23 @@ int main(int argc, char** argv)
{ {
NpdmProcess npdm; NpdmProcess npdm;
npdm.setInputFile(&inputFile); npdm.setInputFile(&inputFile, 0, inputFile.size());
npdm.setKeyset(&user_set.getKeyset()); npdm.setKeyset(&user_set.getKeyset());
npdm.setCliOutputMode(user_set.getCliOutputType()); npdm.setCliOutputMode(user_set.getCliOutputType());
npdm.setVerifyMode(user_set.isVerifyFile()); npdm.setVerifyMode(user_set.isVerifyFile());
npdm.process(); npdm.process();
} }
else if (user_set.getFileType() == FILE_CNMT)
{
CnmtProcess cnmt;
cnmt.setInputFile(&inputFile, 0, inputFile.size());
cnmt.setCliOutputMode(user_set.getCliOutputType());
cnmt.setVerifyMode(user_set.isVerifyFile());
cnmt.process();
}
} }
catch (const fnd::Exception& e) { catch (const fnd::Exception& e) {
printf("\n\n%s\n", e.what()); printf("\n\n%s\n", e.what());

View file

@ -17,7 +17,7 @@ enum FileType
FILE_ROMFS, FILE_ROMFS,
FILE_NCA, FILE_NCA,
FILE_NPDM, FILE_NPDM,
FILE_CMNT, FILE_CNMT,
FILE_INVALID = -1, FILE_INVALID = -1,
}; };