mirror of
https://github.com/jakcron/nstool.git
synced 2024-12-22 18:55:29 +00:00
[nx|ncatool] Take out NCA constants and structures to separate file. Add support for NCA key area index 5.
This commit is contained in:
parent
dec7c50cc4
commit
fd9261b789
|
@ -1,10 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <string>
|
#include <nx/nca.h>
|
||||||
#include <fnd/types.h>
|
|
||||||
#include <fnd/MemoryBlob.h>
|
#include <fnd/MemoryBlob.h>
|
||||||
#include <fnd/List.h>
|
#include <fnd/List.h>
|
||||||
#include <crypto/aes.h>
|
|
||||||
#include <crypto/sha.h>
|
|
||||||
#include <fnd/ISerialiseableBinary.h>
|
#include <fnd/ISerialiseableBinary.h>
|
||||||
|
|
||||||
namespace nx
|
namespace nx
|
||||||
|
@ -19,37 +16,16 @@ namespace nx
|
||||||
NCA3_FORMAT
|
NCA3_FORMAT
|
||||||
};
|
};
|
||||||
|
|
||||||
enum DistributionType
|
struct sPartition
|
||||||
{
|
|
||||||
DIST_DOWNLOAD,
|
|
||||||
DIST_GAME_CARD
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ContentType
|
|
||||||
{
|
|
||||||
TYPE_PROGRAM,
|
|
||||||
TYPE_META,
|
|
||||||
TYPE_CONTROL,
|
|
||||||
TYPE_MANUAL,
|
|
||||||
TYPE_DATA,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum KeyBankIndex
|
|
||||||
{
|
|
||||||
KEY_AESXTS_0,
|
|
||||||
KEY_AESXTS_1,
|
|
||||||
KEY_AESCTR,
|
|
||||||
KEY_UNUSED_3,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sSection
|
|
||||||
{
|
{
|
||||||
|
byte_t index;
|
||||||
uint64_t offset;
|
uint64_t offset;
|
||||||
uint64_t size;
|
uint64_t size;
|
||||||
crypto::sha::sSha256Hash hash;
|
crypto::sha::sSha256Hash hash;
|
||||||
|
|
||||||
const sSection& operator=(const sSection& other)
|
const sPartition& operator=(const sPartition& other)
|
||||||
{
|
{
|
||||||
|
index = other.index;
|
||||||
offset = other.offset;
|
offset = other.offset;
|
||||||
size = other.size;
|
size = other.size;
|
||||||
hash = other.hash;
|
hash = other.hash;
|
||||||
|
@ -57,21 +33,20 @@ namespace nx
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const sSection& other) const
|
bool operator==(const sPartition& other) const
|
||||||
{
|
{
|
||||||
return (offset == other.offset) \
|
return (index == other.index) \
|
||||||
|
&& (offset == other.offset) \
|
||||||
&& (size == other.size) \
|
&& (size == other.size) \
|
||||||
&& (hash == other.hash);
|
&& (hash == other.hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(const sSection& other) const
|
bool operator!=(const sPartition& other) const
|
||||||
{
|
{
|
||||||
return !operator==(other);
|
return !operator==(other);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static const size_t kBlockSize = 0x200;
|
|
||||||
|
|
||||||
NcaHeader();
|
NcaHeader();
|
||||||
NcaHeader(const NcaHeader& other);
|
NcaHeader(const NcaHeader& other);
|
||||||
NcaHeader(const byte_t* bytes, size_t len);
|
NcaHeader(const byte_t* bytes, size_t len);
|
||||||
|
@ -92,85 +67,56 @@ namespace nx
|
||||||
void clear();
|
void clear();
|
||||||
FormatVersion getFormatVersion() const;
|
FormatVersion getFormatVersion() const;
|
||||||
void setFormatVersion(FormatVersion ver);
|
void setFormatVersion(FormatVersion ver);
|
||||||
DistributionType getDistributionType() const;
|
nca::DistributionType getDistributionType() const;
|
||||||
void setDistributionType(DistributionType type);
|
void setDistributionType(nca::DistributionType type);
|
||||||
ContentType getContentType() const;
|
nca::ContentType getContentType() const;
|
||||||
void setContentType(ContentType type);
|
void setContentType(nca::ContentType type);
|
||||||
byte_t getCryptoType() const;
|
byte_t getKeyGeneration() const;
|
||||||
void setCryptoType(byte_t type);
|
void setKeyGeneration(byte_t gen);
|
||||||
byte_t getKaekIndex() const;
|
byte_t getKaekIndex() const;
|
||||||
void setKaekIndex(byte_t index);
|
void setKaekIndex(byte_t index);
|
||||||
uint64_t getNcaSize() const;
|
uint64_t getContentSize() const;
|
||||||
void setNcaSize(uint64_t size);
|
void setContentSize(uint64_t size);
|
||||||
uint64_t getProgramId() const;
|
uint64_t getProgramId() const;
|
||||||
void setProgramId(uint64_t program_id);
|
void setProgramId(uint64_t program_id);
|
||||||
uint32_t getContentIndex() const;
|
uint32_t getContentIndex() const;
|
||||||
void setContentIndex(uint32_t index);
|
void setContentIndex(uint32_t index);
|
||||||
uint32_t getSdkAddonVersion() const;
|
uint32_t getSdkAddonVersion() const;
|
||||||
void setSdkAddonVersion(uint32_t version);
|
void setSdkAddonVersion(uint32_t version);
|
||||||
const fnd::List<sSection>& getSections() const;
|
const byte_t* getRightsId() const;
|
||||||
void addSection(const sSection& section);
|
void setRightsId(const byte_t* rights_id);
|
||||||
|
const fnd::List<sPartition>& getPartitions() const;
|
||||||
|
void setPartitions(const fnd::List<sPartition>& partitions);
|
||||||
const fnd::List<crypto::aes::sAes128Key>& getEncAesKeys() const;
|
const fnd::List<crypto::aes::sAes128Key>& getEncAesKeys() const;
|
||||||
void addEncAesKey(const crypto::aes::sAes128Key& key);
|
void setEncAesKeys(const fnd::List<crypto::aes::sAes128Key>& keys);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const std::string kModuleName = "NCA_HEADER";
|
const std::string kModuleName = "NCA_HEADER";
|
||||||
const std::string kNca2Sig = "NCA2";
|
|
||||||
const std::string kNca3Sig = "NCA3";
|
//static const uint32_t kDefaultSdkAddonVersion = 721920;
|
||||||
static const size_t kSectionNum = 4;
|
|
||||||
static const size_t kAesKeyNum = 4;
|
|
||||||
static const uint32_t kDefaultSdkAddonVersion = 721920;
|
|
||||||
|
|
||||||
enum ProgramPartitionId
|
enum ProgramPartitionId
|
||||||
{
|
{
|
||||||
SECTION_CODE,
|
PARTITION_CODE,
|
||||||
SECTION_DATA,
|
PARTITION_DATA,
|
||||||
SECTION_LOGO,
|
PARTITION_LOGO,
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma pack (push, 1)
|
|
||||||
|
|
||||||
struct sNcaHeader
|
|
||||||
{
|
|
||||||
char signature[4];
|
|
||||||
byte_t distribution_type;
|
|
||||||
byte_t content_type;
|
|
||||||
byte_t crypto_type; // KeyGeneration
|
|
||||||
byte_t key_area_encryption_key_index;
|
|
||||||
le_uint64_t nca_size;
|
|
||||||
le_uint64_t program_id;
|
|
||||||
le_uint32_t content_index;
|
|
||||||
le_uint32_t sdk_addon_version;
|
|
||||||
byte_t crypto_type_2;
|
|
||||||
byte_t reserved_2[0xf];
|
|
||||||
byte_t rights_id[0x10];
|
|
||||||
struct sNcaSection
|
|
||||||
{
|
|
||||||
le_uint32_t start; // block units
|
|
||||||
le_uint32_t end; // block units
|
|
||||||
byte_t enabled;
|
|
||||||
byte_t reserved[7];
|
|
||||||
} section[kSectionNum];
|
|
||||||
crypto::sha::sSha256Hash section_hash[kSectionNum];
|
|
||||||
crypto::aes::sAes128Key enc_aes_key[kAesKeyNum];
|
|
||||||
|
|
||||||
};
|
|
||||||
#pragma pack (pop)
|
|
||||||
|
|
||||||
// binary
|
// binary
|
||||||
fnd::MemoryBlob mBinaryBlob;
|
fnd::MemoryBlob mBinaryBlob;
|
||||||
|
|
||||||
// data
|
// data
|
||||||
FormatVersion mFormatVersion;
|
FormatVersion mFormatVersion;
|
||||||
DistributionType mDistributionType;
|
nca::DistributionType mDistributionType;
|
||||||
ContentType mContentType;
|
nca::ContentType mContentType;
|
||||||
byte_t mCryptoType;
|
byte_t mKeyGeneration;
|
||||||
byte_t mKaekIndex;
|
byte_t mKaekIndex;
|
||||||
uint64_t mNcaSize;
|
uint64_t mContentSize;
|
||||||
uint64_t mProgramId;
|
uint64_t mProgramId;
|
||||||
uint32_t mContentIndex;
|
uint32_t mContentIndex;
|
||||||
uint32_t mSdkAddonVersion;
|
uint32_t mSdkAddonVersion;
|
||||||
fnd::List<sSection> mSections;
|
byte_t mRightsId[nca::kRightsIdLen];
|
||||||
|
fnd::List<sPartition> mPartitions;
|
||||||
fnd::List<crypto::aes::sAes128Key> mEncAesKeys;
|
fnd::List<crypto::aes::sAes128Key> mEncAesKeys;
|
||||||
|
|
||||||
uint64_t blockNumToSize(uint32_t block_num) const;
|
uint64_t blockNumToSize(uint32_t block_num) const;
|
||||||
|
|
141
lib/libnx/include/nx/nca.h
Normal file
141
lib/libnx/include/nx/nca.h
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
#include <string>
|
||||||
|
#include <fnd/types.h>
|
||||||
|
#include <crypto/aes.h>
|
||||||
|
#include <crypto/sha.h>
|
||||||
|
#include <fnd/ISerialiseableBinary.h>
|
||||||
|
|
||||||
|
namespace nx
|
||||||
|
{
|
||||||
|
namespace nca
|
||||||
|
{
|
||||||
|
const std::string kNca2Sig = "NCA2";
|
||||||
|
const std::string kNca3Sig = "NCA3";
|
||||||
|
static const size_t kSectorSize = 0x200;
|
||||||
|
static const size_t kPartitionNum = 4;
|
||||||
|
static const size_t kHeaderSectorNum = 6;
|
||||||
|
static const size_t kHeaderSize = kSectorSize * kHeaderSectorNum;
|
||||||
|
static const size_t kAesKeyNum = 16;
|
||||||
|
static const size_t kRightsIdLen = 0x10;
|
||||||
|
|
||||||
|
enum DistributionType
|
||||||
|
{
|
||||||
|
DIST_DOWNLOAD,
|
||||||
|
DIST_GAME_CARD
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ContentType
|
||||||
|
{
|
||||||
|
TYPE_PROGRAM,
|
||||||
|
TYPE_META,
|
||||||
|
TYPE_CONTROL,
|
||||||
|
TYPE_MANUAL,
|
||||||
|
TYPE_DATA,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum KeyBankIndex
|
||||||
|
{
|
||||||
|
KEY_AESXTS_0,
|
||||||
|
KEY_AESXTS_1,
|
||||||
|
KEY_AESCTR,
|
||||||
|
KEY_UNUSED_3,
|
||||||
|
KEY_AESCTR_HW
|
||||||
|
};
|
||||||
|
|
||||||
|
enum KeyAreaEncryptionKeyIndex
|
||||||
|
{
|
||||||
|
KAEK_IDX_APPLICATION,
|
||||||
|
KAEK_IDX_OCEAN,
|
||||||
|
KAEK_IDX_SYSTEM
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FormatType
|
||||||
|
{
|
||||||
|
FORMAT_ROMFS,
|
||||||
|
FORMAT_PFS0
|
||||||
|
};
|
||||||
|
|
||||||
|
enum HashType
|
||||||
|
{
|
||||||
|
HASH_AUTO,
|
||||||
|
HASH_UNK1,
|
||||||
|
HASH_HIERARCHICAL_SHA256,
|
||||||
|
HASH_HIERARCHICAL_INTERGRITY
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EncryptionType
|
||||||
|
{
|
||||||
|
CRYPT_AUTO,
|
||||||
|
CRYPT_NONE,
|
||||||
|
CRYPT_AESXTS,
|
||||||
|
CRYPT_AESCTR,
|
||||||
|
CRYPT_BKTR
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma pack(push,1)
|
||||||
|
struct sNcaHeader
|
||||||
|
{
|
||||||
|
char signature[4];
|
||||||
|
byte_t distribution_type;
|
||||||
|
byte_t content_type;
|
||||||
|
byte_t key_generation; // KeyGeneration
|
||||||
|
byte_t key_area_encryption_key_index;
|
||||||
|
le_uint64_t content_size;
|
||||||
|
le_uint64_t program_id;
|
||||||
|
le_uint32_t content_index;
|
||||||
|
le_uint32_t sdk_addon_version;
|
||||||
|
byte_t key_generation_2;
|
||||||
|
byte_t reserved_2[0xf];
|
||||||
|
byte_t rights_id[nca::kRightsIdLen];
|
||||||
|
struct sNcaSection
|
||||||
|
{
|
||||||
|
le_uint32_t start; // block units
|
||||||
|
le_uint32_t end; // block units
|
||||||
|
byte_t enabled;
|
||||||
|
byte_t reserved[7];
|
||||||
|
} partition[nca::kPartitionNum];
|
||||||
|
crypto::sha::sSha256Hash partition_hash[nca::kPartitionNum];
|
||||||
|
crypto::aes::sAes128Key enc_aes_key[nca::kAesKeyNum];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sNcaFsHeader
|
||||||
|
{
|
||||||
|
le_uint16_t version; // usually 0x0002
|
||||||
|
byte_t format_type; // RomFs(0x00), PartitionFs(0x01)
|
||||||
|
byte_t hash_type; // HashTypeAuto(0x00), HashTypeHierarchicalSha256(0x02), HashTypeHierarchicalIntegrity(0x03).RomFs uses (0x03) this is forced, PartitionFs uses (0x02).
|
||||||
|
byte_t encryption_type; // EncryptionTypeAuto(0x00), EncryptionTypeNone(0x01), EncryptionTypeAesCtr(0x03)
|
||||||
|
byte_t reserved[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sPfsSuperBlock
|
||||||
|
{
|
||||||
|
byte_t master_hash[0x20];
|
||||||
|
le_uint32_t hash_block_size;
|
||||||
|
le_uint32_t unk_0x02;
|
||||||
|
struct sLayout
|
||||||
|
{
|
||||||
|
le_uint64_t offset;
|
||||||
|
le_uint64_t size;
|
||||||
|
} hash_data, hash_target;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const size_t kMaxIvfcLevel = 4;
|
||||||
|
|
||||||
|
struct sIvfcHeader
|
||||||
|
{
|
||||||
|
le_uint32_t magic;
|
||||||
|
le_uint32_t id;
|
||||||
|
le_uint32_t master_hash_size;
|
||||||
|
le_uint32_t level_num;
|
||||||
|
struct sIvfcLevelHeader
|
||||||
|
{
|
||||||
|
uint64_t logical_offset;
|
||||||
|
uint64_t hash_data_size;
|
||||||
|
uint32_t block_size;
|
||||||
|
byte_t reserved[4];
|
||||||
|
} level_header[kMaxIvfcLevel];
|
||||||
|
byte_t unk_0xA0[0x20];
|
||||||
|
byte_t master_hash[0x20];
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
}
|
|
@ -12,38 +12,49 @@ void NcaHeader::exportBinary()
|
||||||
switch(mFormatVersion)
|
switch(mFormatVersion)
|
||||||
{
|
{
|
||||||
case (NCA2_FORMAT):
|
case (NCA2_FORMAT):
|
||||||
strncpy(hdr->signature, kNca2Sig.c_str(), 4);
|
strncpy(hdr->signature, nca::kNca2Sig.c_str(), 4);
|
||||||
break;
|
break;
|
||||||
case (NCA3_FORMAT):
|
case (NCA3_FORMAT):
|
||||||
strncpy(hdr->signature, kNca3Sig.c_str(), 4);
|
strncpy(hdr->signature, nca::kNca3Sig.c_str(), 4);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw fnd::Exception(kModuleName, "Unsupported format version");
|
throw fnd::Exception(kModuleName, "Unsupported format version");
|
||||||
}
|
}
|
||||||
hdr->distribution_type = mDistributionType;
|
hdr->distribution_type = mDistributionType;
|
||||||
hdr->content_type = mContentType;
|
hdr->content_type = mContentType;
|
||||||
hdr->crypto_type = mCryptoType;
|
if (mKeyGeneration > 2)
|
||||||
|
{
|
||||||
|
hdr->key_generation = 2;
|
||||||
|
hdr->key_generation_2 = mKeyGeneration;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hdr->key_generation = mKeyGeneration;
|
||||||
|
hdr->key_generation_2 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
hdr->key_area_encryption_key_index = mKaekIndex;
|
hdr->key_area_encryption_key_index = mKaekIndex;
|
||||||
hdr->nca_size = mNcaSize;
|
hdr->content_size = mContentSize;
|
||||||
hdr->program_id = mProgramId;
|
hdr->program_id = mProgramId;
|
||||||
hdr->content_index = mContentIndex;
|
hdr->content_index = mContentIndex;
|
||||||
hdr->sdk_addon_version = mSdkAddonVersion;
|
hdr->sdk_addon_version = mSdkAddonVersion;
|
||||||
hdr->crypto_type_2 = 0;
|
memcpy(hdr->rights_id, mRightsId, nca::kRightsIdLen);
|
||||||
|
|
||||||
// TODO: properly reconstruct NCA layout? atm in hands of user
|
// TODO: properly reconstruct NCA layout? atm in hands of user
|
||||||
|
for (size_t i = 0; i < mPartitions.getSize(); i++)
|
||||||
for (size_t i = 0; i < mSections.getSize(); i++)
|
|
||||||
{
|
{
|
||||||
// determine section index
|
// determine partition index
|
||||||
byte_t section = mSections.getSize() - 1 - i;
|
byte_t idx = mPartitions[i].index;
|
||||||
|
|
||||||
hdr->section[section].start = sizeToBlockNum(mSections[i].offset);
|
if (mPartitions[i].index >= nca::kPartitionNum || hdr->partition[idx].enabled) continue;
|
||||||
hdr->section[section].end = (sizeToBlockNum(mSections[i].offset) + sizeToBlockNum(mSections[i].size));
|
|
||||||
hdr->section[section].enabled = true;
|
hdr->partition[idx].start = sizeToBlockNum(mPartitions[i].offset);
|
||||||
hdr->section_hash[section] = mSections[i].hash;
|
hdr->partition[idx].end = (sizeToBlockNum(mPartitions[i].offset) + sizeToBlockNum(mPartitions[i].size));
|
||||||
|
hdr->partition[idx].enabled = true;
|
||||||
|
hdr->partition_hash[idx] = mPartitions[i].hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < kAesKeyNum; i++)
|
for (size_t i = 0; i < nca::kAesKeyNum; i++)
|
||||||
{
|
{
|
||||||
hdr->enc_aes_key[i] = mEncAesKeys[i];
|
hdr->enc_aes_key[i] = mEncAesKeys[i];
|
||||||
}
|
}
|
||||||
|
@ -63,11 +74,11 @@ void NcaHeader::importBinary(const byte_t * bytes, size_t len)
|
||||||
|
|
||||||
sNcaHeader* hdr = (sNcaHeader*)mBinaryBlob.getBytes();
|
sNcaHeader* hdr = (sNcaHeader*)mBinaryBlob.getBytes();
|
||||||
|
|
||||||
if (memcmp(hdr->signature, kNca2Sig.c_str(), 4) == 0)
|
if (memcmp(hdr->signature, nca::kNca2Sig.c_str(), 4) == 0)
|
||||||
{
|
{
|
||||||
mFormatVersion = NCA2_FORMAT;
|
mFormatVersion = NCA2_FORMAT;
|
||||||
}
|
}
|
||||||
else if (memcmp(hdr->signature, kNca3Sig.c_str(), 4) == 0)
|
else if (memcmp(hdr->signature, nca::kNca3Sig.c_str(), 4) == 0)
|
||||||
{
|
{
|
||||||
mFormatVersion = NCA3_FORMAT;
|
mFormatVersion = NCA3_FORMAT;
|
||||||
}
|
}
|
||||||
|
@ -76,28 +87,26 @@ void NcaHeader::importBinary(const byte_t * bytes, size_t len)
|
||||||
throw fnd::Exception(kModuleName, "NCA header corrupt");
|
throw fnd::Exception(kModuleName, "NCA header corrupt");
|
||||||
}
|
}
|
||||||
|
|
||||||
mDistributionType = (DistributionType)hdr->distribution_type;
|
mDistributionType = (nca::DistributionType)hdr->distribution_type;
|
||||||
mContentType = (ContentType)hdr->content_type;
|
mContentType = (nca::ContentType)hdr->content_type;
|
||||||
mCryptoType = MAX(hdr->crypto_type, hdr->crypto_type_2);
|
mKeyGeneration = MAX(hdr->key_generation, hdr->key_generation_2);
|
||||||
mKaekIndex = hdr->key_area_encryption_key_index;
|
mKaekIndex = hdr->key_area_encryption_key_index;
|
||||||
mNcaSize = *hdr->nca_size;
|
mContentSize = *hdr->content_size;
|
||||||
mProgramId = *hdr->program_id;
|
mProgramId = *hdr->program_id;
|
||||||
mContentIndex = *hdr->content_index;
|
mContentIndex = *hdr->content_index;
|
||||||
mSdkAddonVersion = *hdr->sdk_addon_version;
|
mSdkAddonVersion = *hdr->sdk_addon_version;
|
||||||
|
memcpy(mRightsId, hdr->rights_id, nca::kRightsIdLen);
|
||||||
|
|
||||||
for (size_t i = 0; i < kSectionNum; i++)
|
for (size_t i = 0; i < nca::kPartitionNum; i++)
|
||||||
{
|
{
|
||||||
// determine section index
|
|
||||||
byte_t section = kSectionNum - 1 - i;
|
|
||||||
|
|
||||||
// skip sections that don't exist
|
// skip sections that don't exist
|
||||||
if (*hdr->section[section].start == 0 && *hdr->section[section].end == 0) continue;
|
if (hdr->partition[i].enabled == 0) continue;
|
||||||
|
|
||||||
// add high level struct
|
// add high level struct
|
||||||
mSections.addElement({ blockNumToSize(*hdr->section[section].start), blockNumToSize(hdr->section[section].end.get() - hdr->section[section].start.get()), hdr->section_hash[section] });
|
mPartitions.addElement({(byte_t)i, blockNumToSize(hdr->partition[i].start.get()), blockNumToSize(hdr->partition[i].end.get() - hdr->partition[i].start.get()), hdr->partition_hash[i] });
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < kAesKeyNum; i++)
|
for (size_t i = 0; i < nca::kAesKeyNum; i++)
|
||||||
{
|
{
|
||||||
mEncAesKeys.addElement(hdr->enc_aes_key[i]);
|
mEncAesKeys.addElement(hdr->enc_aes_key[i]);
|
||||||
}
|
}
|
||||||
|
@ -106,15 +115,16 @@ void NcaHeader::importBinary(const byte_t * bytes, size_t len)
|
||||||
void nx::NcaHeader::clear()
|
void nx::NcaHeader::clear()
|
||||||
{
|
{
|
||||||
mFormatVersion = NCA3_FORMAT;
|
mFormatVersion = NCA3_FORMAT;
|
||||||
mDistributionType = DIST_DOWNLOAD;
|
mDistributionType = nca::DIST_DOWNLOAD;
|
||||||
mContentType = TYPE_PROGRAM;
|
mContentType = nca::TYPE_PROGRAM;
|
||||||
mCryptoType = 0;
|
mKeyGeneration = 0;
|
||||||
mKaekIndex = 0;
|
mKaekIndex = 0;
|
||||||
mNcaSize = 0;
|
mContentSize = 0;
|
||||||
mProgramId = 0;
|
mProgramId = 0;
|
||||||
mContentIndex = 0;
|
mContentIndex = 0;
|
||||||
mSdkAddonVersion = 0;
|
mSdkAddonVersion = 0;
|
||||||
mSections.clear();
|
|
||||||
|
mPartitions.clear();
|
||||||
mEncAesKeys.clear();
|
mEncAesKeys.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,34 +138,34 @@ void nx::NcaHeader::setFormatVersion(FormatVersion version)
|
||||||
mFormatVersion = version;
|
mFormatVersion = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
nx::NcaHeader::DistributionType nx::NcaHeader::getDistributionType() const
|
nx::nca::DistributionType nx::NcaHeader::getDistributionType() const
|
||||||
{
|
{
|
||||||
return mDistributionType;
|
return mDistributionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void nx::NcaHeader::setDistributionType(DistributionType type)
|
void nx::NcaHeader::setDistributionType(nca::DistributionType type)
|
||||||
{
|
{
|
||||||
mDistributionType = type;
|
mDistributionType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
nx::NcaHeader::ContentType nx::NcaHeader::getContentType() const
|
nx::nca::ContentType nx::NcaHeader::getContentType() const
|
||||||
{
|
{
|
||||||
return mContentType;
|
return mContentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void nx::NcaHeader::setContentType(ContentType type)
|
void nx::NcaHeader::setContentType(nca::ContentType type)
|
||||||
{
|
{
|
||||||
mContentType = type;
|
mContentType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte_t nx::NcaHeader::getCryptoType() const
|
byte_t nx::NcaHeader::getKeyGeneration() const
|
||||||
{
|
{
|
||||||
return mCryptoType;
|
return mKeyGeneration;
|
||||||
}
|
}
|
||||||
|
|
||||||
void nx::NcaHeader::setCryptoType(byte_t type)
|
void nx::NcaHeader::setKeyGeneration(byte_t gen)
|
||||||
{
|
{
|
||||||
mCryptoType = type;
|
mKeyGeneration = gen;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte_t nx::NcaHeader::getKaekIndex() const
|
byte_t nx::NcaHeader::getKaekIndex() const
|
||||||
|
@ -168,14 +178,14 @@ void nx::NcaHeader::setKaekIndex(byte_t index)
|
||||||
mKaekIndex = index;
|
mKaekIndex = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t NcaHeader::getNcaSize() const
|
uint64_t NcaHeader::getContentSize() const
|
||||||
{
|
{
|
||||||
return mNcaSize;
|
return mContentSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NcaHeader::setNcaSize(uint64_t size)
|
void NcaHeader::setContentSize(uint64_t size)
|
||||||
{
|
{
|
||||||
mNcaSize = size;
|
mContentSize = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t NcaHeader::getProgramId() const
|
uint64_t NcaHeader::getProgramId() const
|
||||||
|
@ -208,18 +218,28 @@ void nx::NcaHeader::setSdkAddonVersion(uint32_t version)
|
||||||
mSdkAddonVersion = version;
|
mSdkAddonVersion = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fnd::List<NcaHeader::sSection>& NcaHeader::getSections() const
|
const byte_t* nx::NcaHeader::getRightsId() const
|
||||||
{
|
{
|
||||||
return mSections;
|
return mRightsId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NcaHeader::addSection(const sSection & section)
|
void nx::NcaHeader::setRightsId(const byte_t* rights_id)
|
||||||
{
|
{
|
||||||
if (mSections.getSize() >= kSectionNum)
|
memcpy(mRightsId, rights_id, nca::kRightsIdLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fnd::List<NcaHeader::sPartition>& NcaHeader::getPartitions() const
|
||||||
|
{
|
||||||
|
return mPartitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaHeader::setPartitions(const fnd::List<NcaHeader::sPartition>& partitions)
|
||||||
|
{
|
||||||
|
mPartitions = partitions;
|
||||||
|
if (mPartitions.getSize() >= nca::kPartitionNum)
|
||||||
{
|
{
|
||||||
throw fnd::Exception(kModuleName, "Too many NCA sections");
|
throw fnd::Exception(kModuleName, "Too many NCA partitions");
|
||||||
}
|
}
|
||||||
mSections.addElement(section);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fnd::List<crypto::aes::sAes128Key>& NcaHeader::getEncAesKeys() const
|
const fnd::List<crypto::aes::sAes128Key>& NcaHeader::getEncAesKeys() const
|
||||||
|
@ -227,37 +247,32 @@ const fnd::List<crypto::aes::sAes128Key>& NcaHeader::getEncAesKeys() const
|
||||||
return mEncAesKeys;
|
return mEncAesKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NcaHeader::addEncAesKey(const crypto::aes::sAes128Key & key)
|
void NcaHeader::setEncAesKeys(const fnd::List<crypto::aes::sAes128Key>& keys)
|
||||||
{
|
{
|
||||||
if (mEncAesKeys.getSize() >= kAesKeyNum)
|
mEncAesKeys = keys;
|
||||||
{
|
|
||||||
throw fnd::Exception(kModuleName, "Too many NCA aes keys");
|
|
||||||
}
|
|
||||||
|
|
||||||
mEncAesKeys.addElement(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t NcaHeader::blockNumToSize(uint32_t block_num) const
|
uint64_t NcaHeader::blockNumToSize(uint32_t block_num) const
|
||||||
{
|
{
|
||||||
return block_num*kBlockSize;
|
return block_num*nca::kSectorSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t NcaHeader::sizeToBlockNum(uint64_t real_size) const
|
uint32_t NcaHeader::sizeToBlockNum(uint64_t real_size) const
|
||||||
{
|
{
|
||||||
return align(real_size, kBlockSize)/kBlockSize;
|
return align(real_size, nca::kSectorSize) / nca::kSectorSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NcaHeader::isEqual(const NcaHeader & other) const
|
bool NcaHeader::isEqual(const NcaHeader & other) const
|
||||||
{
|
{
|
||||||
return (mDistributionType == other.mDistributionType) \
|
return (mDistributionType == other.mDistributionType) \
|
||||||
&& (mContentType == other.mContentType) \
|
&& (mContentType == other.mContentType) \
|
||||||
&& (mCryptoType == other.mCryptoType) \
|
&& (mKeyGeneration == other.mKeyGeneration) \
|
||||||
&& (mKaekIndex == other.mKaekIndex) \
|
&& (mKaekIndex == other.mKaekIndex) \
|
||||||
&& (mNcaSize == other.mNcaSize) \
|
&& (mContentSize == other.mContentSize) \
|
||||||
&& (mProgramId == other.mProgramId) \
|
&& (mProgramId == other.mProgramId) \
|
||||||
&& (mContentIndex == other.mContentIndex) \
|
&& (mContentIndex == other.mContentIndex) \
|
||||||
&& (mSdkAddonVersion == other.mSdkAddonVersion) \
|
&& (mSdkAddonVersion == other.mSdkAddonVersion) \
|
||||||
&& (mSections == other.mSections) \
|
&& (mPartitions == other.mPartitions) \
|
||||||
&& (mEncAesKeys == other.mEncAesKeys);
|
&& (mEncAesKeys == other.mEncAesKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,13 +287,13 @@ void NcaHeader::copyFrom(const NcaHeader & other)
|
||||||
mBinaryBlob.clear();
|
mBinaryBlob.clear();
|
||||||
mDistributionType = other.mDistributionType;
|
mDistributionType = other.mDistributionType;
|
||||||
mContentType = other.mContentType;
|
mContentType = other.mContentType;
|
||||||
mCryptoType = other.mCryptoType;
|
mKeyGeneration = other.mKeyGeneration;
|
||||||
mKaekIndex = other.mKaekIndex;
|
mKaekIndex = other.mKaekIndex;
|
||||||
mNcaSize = other.mNcaSize;
|
mContentSize = other.mContentSize;
|
||||||
mProgramId = other.mProgramId;
|
mProgramId = other.mProgramId;
|
||||||
mContentIndex = other.mContentIndex;
|
mContentIndex = other.mContentIndex;
|
||||||
mSdkAddonVersion = other.mSdkAddonVersion;
|
mSdkAddonVersion = other.mSdkAddonVersion;
|
||||||
mSections = other.mSections;
|
mPartitions = other.mPartitions;
|
||||||
mEncAesKeys = other.mEncAesKeys;
|
mEncAesKeys = other.mEncAesKeys;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
#include <crypto/aes.h>
|
#include <crypto/aes.h>
|
||||||
#include <fnd/io.h>
|
#include <fnd/io.h>
|
||||||
#include <fnd/MemoryBlob.h>
|
#include <fnd/MemoryBlob.h>
|
||||||
|
#include <fnd/SimpleTextOutput.h>
|
||||||
#include <nx/NXCrypto.h>
|
#include <nx/NXCrypto.h>
|
||||||
#include <nx/NcaHeader.h>
|
#include <nx/NcaHeader.h>
|
||||||
|
#include <nx/PfsHeader.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <direct.h>
|
#include <direct.h>
|
||||||
|
@ -11,8 +13,6 @@
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const size_t kNcaSectorSize = nx::NcaHeader::kBlockSize;
|
|
||||||
|
|
||||||
std::string kFormatVersionStr[]
|
std::string kFormatVersionStr[]
|
||||||
{
|
{
|
||||||
"NCA2",
|
"NCA2",
|
||||||
|
@ -40,7 +40,7 @@ std::string kEncryptionTypeStr[]
|
||||||
"None",
|
"None",
|
||||||
"AesXts",
|
"AesXts",
|
||||||
"AesCtr",
|
"AesCtr",
|
||||||
"BKTR"
|
"AesCtrEx"
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string kHashTypeStr[]
|
std::string kHashTypeStr[]
|
||||||
|
@ -64,16 +64,6 @@ std::string kKaekIndexStr[]
|
||||||
"System"
|
"System"
|
||||||
};
|
};
|
||||||
|
|
||||||
enum EncryptionType
|
|
||||||
{
|
|
||||||
CRYPT_AUTO,
|
|
||||||
CRYPT_NONE,
|
|
||||||
CRYPT_AESXTS,
|
|
||||||
CRYPT_AESCTR,
|
|
||||||
CRYPT_BKTR
|
|
||||||
};
|
|
||||||
|
|
||||||
static const byte_t kNcaMagic[2][4] = {{'N','C','A','2'}, {'N','C','A','3'}};
|
|
||||||
|
|
||||||
enum KeysetType
|
enum KeysetType
|
||||||
{
|
{
|
||||||
|
@ -87,18 +77,7 @@ static const byte_t* kNcaHeaderKey[2][2] =
|
||||||
{ crypto::aes::nx::prod::nca_header_key[0], crypto::aes::nx::prod::nca_header_key[1] }
|
{ crypto::aes::nx::prod::nca_header_key[0], crypto::aes::nx::prod::nca_header_key[1] }
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma pack(push,1)
|
inline size_t sectorToOffset(size_t sector_index) { return sector_index * nx::nca::kSectorSize; }
|
||||||
struct sNcaFsHeader
|
|
||||||
{
|
|
||||||
le_uint16_t version; // usually 0x0002
|
|
||||||
byte_t format_type; // RomFs(0x00), PartitionFs(0x01)
|
|
||||||
byte_t hash_type; // HashTypeAuto(0x00), HashTypeHierarchicalSha256(0x02), HashTypeHierarchicalIntegrity(0x03).RomFs uses (0x03) this is forced, PartitionFs uses (0x02).
|
|
||||||
byte_t encryption_type; // EncryptionTypeAuto(0x00), EncryptionTypeNone(0x01), EncryptionTypeAesCtr(0x03)
|
|
||||||
byte_t reserved[3];
|
|
||||||
};
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
inline size_t sectorToOffset(size_t sector_index) { return sector_index * kNcaSectorSize; }
|
|
||||||
|
|
||||||
void initNcaCtr(byte_t ctr[crypto::aes::kAesBlockSize], uint32_t generation)
|
void initNcaCtr(byte_t ctr[crypto::aes::kAesBlockSize], uint32_t generation)
|
||||||
{
|
{
|
||||||
|
@ -109,110 +88,37 @@ void initNcaCtr(byte_t ctr[crypto::aes::kAesBlockSize], uint32_t generation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void hexDump(const byte_t* data, size_t len)
|
void decryptNcaHeader(byte_t header[nx::nca::kHeaderSize], const byte_t* key[2])
|
||||||
{
|
|
||||||
for (size_t i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
printf("%02X", data[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void xorData(const byte_t* a, const byte_t* b, byte_t* out, size_t len)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
out[i] = a[i] ^ b[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void decryptNcaHeader(byte_t header[0xc00], const byte_t* key[2])
|
|
||||||
{
|
{
|
||||||
byte_t tweak[crypto::aes::kAesBlockSize];
|
byte_t tweak[crypto::aes::kAesBlockSize];
|
||||||
|
|
||||||
// decrypt main header
|
// decrypt main header
|
||||||
byte_t raw_hdr[kNcaSectorSize];
|
byte_t raw_hdr[nx::nca::kSectorSize];
|
||||||
nx::NcaHeader hdr;
|
nx::NcaHeader hdr;
|
||||||
crypto::aes::AesXtsMakeTweak(tweak, 1);
|
crypto::aes::AesXtsMakeTweak(tweak, 1);
|
||||||
crypto::aes::AesXtsDecryptSector(header + sectorToOffset(1), kNcaSectorSize, key[0], key[1], tweak, raw_hdr);
|
crypto::aes::AesXtsDecryptSector(header + sectorToOffset(1), nx::nca::kSectorSize, key[0], key[1], tweak, raw_hdr);
|
||||||
hdr.importBinary(raw_hdr, kNcaSectorSize);
|
hdr.importBinary(raw_hdr, nx::nca::kSectorSize);
|
||||||
|
|
||||||
|
bool useNca2SectorIndex = hdr.getFormatVersion() == nx::NcaHeader::NCA2_FORMAT;
|
||||||
|
|
||||||
// decrypt whole header
|
// decrypt whole header
|
||||||
for (size_t i = 0; i < 6; i++)
|
for (size_t i = 0; i < nx::nca::kHeaderSectorNum; i++)
|
||||||
{
|
{
|
||||||
crypto::aes::AesXtsMakeTweak(tweak, (i > 1 && hdr.getFormatVersion() == nx::NcaHeader::NCA2_FORMAT)? 0 : i);
|
crypto::aes::AesXtsMakeTweak(tweak, (i > 1 && useNca2SectorIndex)? 0 : i);
|
||||||
crypto::aes::AesXtsDecryptSector(header + sectorToOffset(i), kNcaSectorSize, key[0], key[1], tweak, header + sectorToOffset(i));
|
crypto::aes::AesXtsDecryptSector(header + sectorToOffset(i), nx::nca::kSectorSize, key[0], key[1], tweak, header + sectorToOffset(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void decryptNcaSectorXts(const fnd::MemoryBlob& nca, byte_t out[kNcaSectorSize], size_t sector, const byte_t* key[2])
|
|
||||||
{
|
|
||||||
byte_t tweak[crypto::aes::kAesBlockSize];
|
|
||||||
crypto::aes::AesXtsMakeTweak(tweak, sector);
|
|
||||||
crypto::aes::AesXtsDecryptSector(nca.getBytes() + sectorToOffset(sector), kNcaSectorSize, key[0], key[1], tweak, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
void decryptNcaSectorCtr(const fnd::MemoryBlob& nca, byte_t out[kNcaSectorSize], size_t sector, const byte_t* key)
|
|
||||||
{
|
|
||||||
byte_t ctr[crypto::aes::kAesBlockSize];
|
|
||||||
initNcaCtr(ctr, 0);
|
|
||||||
crypto::aes::AesIncrementCounter(ctr, (sector*kNcaSectorSize)/crypto::aes::kAesBlockSize, ctr);
|
|
||||||
crypto::aes::AesCtr(nca.getBytes() + sector*kNcaSectorSize, kNcaSectorSize, key, ctr, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
void dumpNcaSector(byte_t out[kNcaSectorSize])
|
|
||||||
{
|
|
||||||
for (size_t j = 0; j < kNcaSectorSize / crypto::aes::kAesBlockSize; j++)
|
|
||||||
{
|
|
||||||
hexDump(out + j * crypto::aes::kAesBlockSize, crypto::aes::kAesBlockSize);
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void dumpHxdStyleSector(byte_t* out, size_t len)
|
|
||||||
{
|
|
||||||
// iterate over 0x10 blocks
|
|
||||||
for (size_t i = 0; i < (len / crypto::aes::kAesBlockSize); i++)
|
|
||||||
{
|
|
||||||
// for block i print each byte
|
|
||||||
for (size_t j = 0; j < crypto::aes::kAesBlockSize; j++)
|
|
||||||
{
|
|
||||||
printf("%02X ", out[i*crypto::aes::kAesBlockSize + j]);
|
|
||||||
}
|
|
||||||
printf(" ");
|
|
||||||
for (size_t j = 0; j < crypto::aes::kAesBlockSize; j++)
|
|
||||||
{
|
|
||||||
printf("%c", isalnum(out[i*crypto::aes::kAesBlockSize + j]) ? out[i*crypto::aes::kAesBlockSize + j] : '.');
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
for (size_t i = 0; i < len % crypto::aes::kAesBlockSize; i++)
|
|
||||||
{
|
|
||||||
printf("%02X ", out[(len / crypto::aes::kAesBlockSize)*crypto::aes::kAesBlockSize + i]);
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < crypto::aes::kAesBlockSize - (len % crypto::aes::kAesBlockSize); i++)
|
|
||||||
{
|
|
||||||
printf(" ");
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < len % crypto::aes::kAesBlockSize; i++)
|
|
||||||
{
|
|
||||||
printf("%c", out[(len / crypto::aes::kAesBlockSize)*crypto::aes::kAesBlockSize + i]);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool testNcaHeaderKey(const byte_t* header_src, const byte_t* key[2])
|
bool testNcaHeaderKey(const byte_t* header_src, const byte_t* key[2])
|
||||||
{
|
{
|
||||||
bool validKey = false;
|
bool validKey = false;
|
||||||
byte_t header_dec[kNcaSectorSize];
|
byte_t header_dec[nx::nca::kSectorSize];
|
||||||
byte_t tweak[crypto::aes::kAesBlockSize];
|
byte_t tweak[crypto::aes::kAesBlockSize];
|
||||||
|
|
||||||
// try key
|
// try key
|
||||||
crypto::aes::AesXtsMakeTweak(tweak, 1);
|
crypto::aes::AesXtsMakeTweak(tweak, 1);
|
||||||
crypto::aes::AesXtsDecryptSector(header_src + sectorToOffset(1), kNcaSectorSize, key[0], key[1], tweak, header_dec);
|
crypto::aes::AesXtsDecryptSector(header_src + sectorToOffset(1), nx::nca::kSectorSize, key[0], key[1], tweak, header_dec);
|
||||||
if (memcmp(header_dec, kNcaMagic[0], 4) == 0 || memcmp(header_dec, kNcaMagic[1], 4) == 0)
|
if (memcmp(header_dec, nx::nca::kNca2Sig.c_str(), 4) == 0 || memcmp(header_dec, nx::nca::kNca3Sig.c_str(), 4) == 0)
|
||||||
{
|
{
|
||||||
validKey = true;
|
validKey = true;
|
||||||
}
|
}
|
||||||
|
@ -233,6 +139,86 @@ KeysetType getKeysetFromNcaHeader(const byte_t* header_src)
|
||||||
throw fnd::Exception("Failed to determine NCA header key");
|
throw fnd::Exception("Failed to determine NCA header key");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void printHeader(const byte_t* header)
|
||||||
|
{
|
||||||
|
nx::NcaHeader hdr;
|
||||||
|
hdr.importBinary(header + sectorToOffset(1), nx::nca::kSectorSize);
|
||||||
|
|
||||||
|
printf("[NCA Header]\n");
|
||||||
|
printf(" Format Type: %s\n", kFormatVersionStr[hdr.getFormatVersion()].c_str());
|
||||||
|
printf(" Dist. Type: %s\n", kDistributionTypeStr[hdr.getDistributionType()].c_str());
|
||||||
|
printf(" Content Type: %s\n", kContentTypeStr[hdr.getContentType()].c_str());
|
||||||
|
printf(" Key Generation: %d\n", hdr.getKeyGeneration());
|
||||||
|
printf(" Kaek Index: %s (%d)\n", kKaekIndexStr[hdr.getKaekIndex()].c_str(), hdr.getKaekIndex());
|
||||||
|
printf(" Size: 0x%" PRIx64 "\n", hdr.getContentSize());
|
||||||
|
printf(" ProgID: 0x%016" PRIx64 "\n", hdr.getProgramId());
|
||||||
|
printf(" Content Index: %" PRIu32 "\n", hdr.getContentIndex());
|
||||||
|
uint32_t ver = hdr.getSdkAddonVersion();
|
||||||
|
printf(" SdkAddon Ver.: v%d.%d.%d (v%" PRIu32 ")\n", (ver>>24 & 0xff),(ver>>16 & 0xff),(ver>>8 & 0xff), ver);
|
||||||
|
printf(" RightsId: ");
|
||||||
|
fnd::SimpleTextOutput::hexDump(hdr.getRightsId(), 0x10);
|
||||||
|
printf("\n");
|
||||||
|
printf(" Encrypted Key Area:\n");
|
||||||
|
crypto::aes::sAes128Key zero_key;
|
||||||
|
memset(zero_key.key, 0, sizeof(zero_key));
|
||||||
|
for (size_t i = 0; i < hdr.getEncAesKeys().getSize(); i++)
|
||||||
|
{
|
||||||
|
if (hdr.getEncAesKeys()[i] != zero_key)
|
||||||
|
{
|
||||||
|
printf(" %2lu: ", i);
|
||||||
|
fnd::SimpleTextOutput::hexDump(hdr.getEncAesKeys()[i].key, crypto::aes::kAes128KeySize);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(" Sections:\n");
|
||||||
|
for (size_t i = 0; i < hdr.getPartitions().getSize(); i++)
|
||||||
|
{
|
||||||
|
const nx::NcaHeader::sPartition& partition = hdr.getPartitions()[i];
|
||||||
|
printf(" %lu:\n", i);
|
||||||
|
//printf(" Start Blk: %" PRId32 "\n", partition.start_blk);
|
||||||
|
//printf(" End Blk: %" PRId32 "\n", partition.end_blk);
|
||||||
|
printf(" Index: %d\n", partition.index);
|
||||||
|
printf(" Offset: 0x%" PRIx64 "\n", partition.offset);
|
||||||
|
printf(" Size: 0x%" PRIx64 "\n", partition.size);
|
||||||
|
|
||||||
|
|
||||||
|
size_t sector_index = 2 + partition.index;
|
||||||
|
|
||||||
|
crypto::sha::sSha256Hash ncaFsHeaderHash;
|
||||||
|
crypto::sha::Sha256(header + sectorToOffset(sector_index), nx::nca::kSectorSize, ncaFsHeaderHash.bytes);
|
||||||
|
if (partition.hash.compare(ncaFsHeaderHash) == false)
|
||||||
|
{
|
||||||
|
throw fnd::Exception("ncatool", "NcaFsHeader has bad sha256 hash");
|
||||||
|
}
|
||||||
|
|
||||||
|
const nx::sNcaFsHeader* fsHdr = (const nx::sNcaFsHeader*)(header + sectorToOffset(sector_index));
|
||||||
|
printf(" FsHeader:\n");
|
||||||
|
printf(" Version: 0x%d\n", fsHdr->version.get());
|
||||||
|
printf(" Format Type: %s\n", kFormatTypeStr[fsHdr->format_type].c_str());
|
||||||
|
printf(" Hash Type: %s\n", kHashTypeStr[fsHdr->hash_type].c_str());
|
||||||
|
printf(" Enc. Type: %s\n", kEncryptionTypeStr[fsHdr->encryption_type].c_str());
|
||||||
|
if (fsHdr->format_type == nx::nca::FORMAT_ROMFS)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (fsHdr->format_type == nx::nca::FORMAT_PFS0)
|
||||||
|
{
|
||||||
|
const nx::sPfsSuperBlock* pfs0 = (const nx::sPfsSuperBlock*)(header + sectorToOffset(sector_index) + sizeof(nx::sNcaFsHeader));
|
||||||
|
printf(" PFS0 SuperBlock:\n");
|
||||||
|
printf(" Master Hash: \n");
|
||||||
|
printf(" HashBlockSize: 0x%x\n", pfs0->hash_block_size.get());
|
||||||
|
printf(" Unknown: 0x%x\n", pfs0->unk_0x02.get());
|
||||||
|
printf(" HashDataOffset: 0x%" PRIx64 "\n", pfs0->hash_data.offset.get());
|
||||||
|
printf(" HashDataSize: 0x%" PRIx64 "\n", pfs0->hash_data.size.get());
|
||||||
|
printf(" HashTargetOffset: 0x%" PRIx64 "\n", pfs0->hash_target.offset.get());
|
||||||
|
printf(" HashTargetSize: 0x%" PRIx64 "\n", pfs0->hash_target.size.get());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
if (argc < 2)
|
if (argc < 2)
|
||||||
|
@ -244,122 +230,13 @@ int main(int argc, char** argv)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
fnd::MemoryBlob nca;
|
fnd::MemoryBlob nca;
|
||||||
fnd::io::readFile(argv[1], nca);
|
fnd::io::readFile(argv[1], 0x0, nx::nca::kHeaderSize, nca);
|
||||||
|
|
||||||
KeysetType keyset = getKeysetFromNcaHeader(nca.getBytes());
|
KeysetType keyset = getKeysetFromNcaHeader(nca.getBytes());
|
||||||
|
|
||||||
decryptNcaHeader(nca.getBytes(), kNcaHeaderKey[keyset]);
|
decryptNcaHeader(nca.getBytes(), kNcaHeaderKey[keyset]);
|
||||||
//dumpHxdStyleSector(nca.getBytes(), 0xc00);
|
|
||||||
|
|
||||||
// nca test
|
printHeader(nca.getBytes());
|
||||||
if (argc == 2 || argc == 3)
|
|
||||||
{
|
|
||||||
//decryptNcaSectorXts(nca, sector, 1, crypto::aes::nx::dev::nca_header_key[0], crypto::aes::nx::dev::nca_header_key[1]);
|
|
||||||
|
|
||||||
nx::NcaHeader hdr;
|
|
||||||
hdr.importBinary(nca.getBytes() + sectorToOffset(1), kNcaSectorSize);
|
|
||||||
|
|
||||||
printf("[NCA Header]\n");
|
|
||||||
printf(" Format Type: %s\n", kFormatVersionStr[hdr.getFormatVersion()].c_str());
|
|
||||||
printf(" Dist. Type: %s\n", kDistributionTypeStr[hdr.getDistributionType()].c_str());
|
|
||||||
printf(" Type: %s\n", kContentTypeStr[hdr.getContentType()].c_str());
|
|
||||||
printf(" Crypto Type: %d\n", hdr.getCryptoType());
|
|
||||||
printf(" Kaek Index: %s (%d)\n", kKaekIndexStr[hdr.getKaekIndex()].c_str(), hdr.getKaekIndex());
|
|
||||||
printf(" Size: 0x%" PRIx64 "\n", hdr.getNcaSize());
|
|
||||||
printf(" ProgID: 0x%016" PRIx64 "\n", hdr.getProgramId());
|
|
||||||
printf(" Content. Idx: %" PRIu32 "\n", hdr.getContentIndex());
|
|
||||||
uint32_t ver = hdr.getSdkAddonVersion();
|
|
||||||
printf(" SdkAddon Ver.: v%d.%d.%d.%d (v%" PRIu32 ")\n", (ver>>24 & 0xff),(ver>>16 & 0xff),(ver>>8 & 0xff),(ver>>0 & 0xff), ver);
|
|
||||||
printf(" Encrypted Key Area:\n");
|
|
||||||
for (size_t i = 0; i < hdr.getEncAesKeys().getSize(); i++)
|
|
||||||
{
|
|
||||||
printf(" %lu: ", i);
|
|
||||||
hexDump(hdr.getEncAesKeys()[i].key, crypto::aes::kAes128KeySize);
|
|
||||||
printf("\n");
|
|
||||||
/*
|
|
||||||
byte_t key[crypto::aes::kAes128KeySize];
|
|
||||||
crypto::aes::AesEcbDecrypt(hdr.getEncAesKeys()[i].key, crypto::aes::kAes128KeySize, crypto::aes::nx::dev::key_area_encryption_key_0, key);
|
|
||||||
printf(" dec: ", i);
|
|
||||||
hexDump(key, crypto::aes::kAes128KeySize);
|
|
||||||
printf("\n");
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
printf(" Sections:\n");
|
|
||||||
for (size_t i = 0; i < hdr.getSections().getSize(); i++)
|
|
||||||
{
|
|
||||||
const nx::NcaHeader::sSection& section = hdr.getSections()[i];
|
|
||||||
printf(" %lu:\n", i);
|
|
||||||
//printf(" Start Blk: %" PRId32 "\n", section.start_blk);
|
|
||||||
//printf(" End Blk: %" PRId32 "\n", section.end_blk);
|
|
||||||
printf(" Offset: 0x%" PRIx64 "\n", section.offset);
|
|
||||||
printf(" Size: 0x%" PRIx64 "\n", section.size);
|
|
||||||
|
|
||||||
|
|
||||||
size_t sector_index = 1 + (hdr.getSections().getSize() - i);
|
|
||||||
|
|
||||||
byte_t hash[crypto::sha::kSha256HashLen];
|
|
||||||
crypto::sha::Sha256(nca.getBytes() + sectorToOffset(sector_index), kNcaSectorSize, hash);
|
|
||||||
if (section.hash.compare(hash) == false)
|
|
||||||
{
|
|
||||||
throw fnd::Exception("ncatool", "NcaFsHeader has bad sha256 hash");
|
|
||||||
}
|
|
||||||
|
|
||||||
const sNcaFsHeader* fsHdr = (const sNcaFsHeader*)(nca.getBytes() + sectorToOffset(sector_index));
|
|
||||||
printf(" FsHeader:\n");
|
|
||||||
printf(" Version: 0x%d\n", fsHdr->version.get());
|
|
||||||
printf(" Format Type: %s\n", kFormatTypeStr[fsHdr->format_type].c_str());
|
|
||||||
printf(" Hash Type: %s\n", kHashTypeStr[fsHdr->hash_type].c_str());
|
|
||||||
printf(" Enc. Type: %s\n", kEncryptionTypeStr[fsHdr->encryption_type].c_str());
|
|
||||||
/*
|
|
||||||
printf(" Hash: ");
|
|
||||||
hexDump(section.hash.bytes, crypto::sha::kSha256HashLen);
|
|
||||||
printf("\n");
|
|
||||||
byte_t hash[crypto::sha::kSha256HashLen];
|
|
||||||
crypto::sha::Sha256(nca.getBytes() + sectorToOffset(sector_index), kNcaSectorSize, hash);
|
|
||||||
printf(" Hash: ");
|
|
||||||
hexDump(hash, crypto::sha::kSha256HashLen);
|
|
||||||
printf("\n");
|
|
||||||
*/
|
|
||||||
//dumpHxdStyleSector(nca.getBytes() + sectorToOffset(sector_index), 0x10);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef USE_OLD_CODE
|
|
||||||
if (argc == 3)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
_mkdir(argv[2]);
|
|
||||||
#else
|
|
||||||
mkdir(argv[2], S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for (size_t i = 0; i < hdr.getSections().getSize(); i++)
|
|
||||||
{
|
|
||||||
const nx::NcaHeader::sSection& section = hdr.getSections()[i];
|
|
||||||
#ifdef _WIN32
|
|
||||||
fnd::io::writeFile(std::string(argv[2]) + "\\" + std::to_string(i) + ".bin" , nca.getBytes() + section.offset, section.size);
|
|
||||||
#else
|
|
||||||
fnd::io::writeFile(std::string(argv[2]) + "/" + std::to_string(i) + ".bin", nca.getBytes() + section.offset, section.size);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (argc == 4)
|
|
||||||
{
|
|
||||||
printf("decrypt test\n");
|
|
||||||
byte_t sect[kNcaSectorSize];;
|
|
||||||
for (size_t i = 0; i < 6; i++)
|
|
||||||
{
|
|
||||||
decryptNcaSectorXts(nca, sect, i, crypto::aes::nx::dev::nca_header_key[0], crypto::aes::nx::dev::nca_header_key[1]);
|
|
||||||
dumpNcaSector(sect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
} catch (const fnd::Exception& e)
|
} catch (const fnd::Exception& e)
|
||||||
{
|
{
|
||||||
printf("%s\n",e.what());
|
printf("%s\n",e.what());
|
||||||
|
|
Loading…
Reference in a new issue