final commit

This commit is contained in:
jakcron 2017-07-19 00:17:32 +10:00
parent 98c933305d
commit d730c1e3b8
22 changed files with 1050 additions and 74 deletions

View file

@ -13,6 +13,21 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ncatool", "programs\ncatool
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{170B4A09-1B67-4A62-93AB-116EBCFF4A8C}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{170B4A09-1B67-4A62-93AB-116EBCFF4A8C}"
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "npdmtool", "programs\npdmtool\npdmtool.vcxproj", "{550C6AC3-EBE0-46CA-AE6C-EEEB59DDF35C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Programs", "Programs", "{E0863FCC-8E72-490D-BE1B-458F12CA8298}"
ProjectSection(SolutionItems) = preProject
programs\makefile = programs\makefile
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8F6C846D-35E2-47FD-AF42-7A3FD036346E}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
makefile = makefile
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pfstool", "programs\pfstool\pfstool.vcxproj", "{BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64 Debug|x64 = Debug|x64
@ -53,6 +68,22 @@ Global
{7DA88C6F-4470-495D-995A-4F633F3370C1}.Release|x64.Build.0 = Release|x64 {7DA88C6F-4470-495D-995A-4F633F3370C1}.Release|x64.Build.0 = Release|x64
{7DA88C6F-4470-495D-995A-4F633F3370C1}.Release|x86.ActiveCfg = Release|Win32 {7DA88C6F-4470-495D-995A-4F633F3370C1}.Release|x86.ActiveCfg = Release|Win32
{7DA88C6F-4470-495D-995A-4F633F3370C1}.Release|x86.Build.0 = Release|Win32 {7DA88C6F-4470-495D-995A-4F633F3370C1}.Release|x86.Build.0 = Release|Win32
{550C6AC3-EBE0-46CA-AE6C-EEEB59DDF35C}.Debug|x64.ActiveCfg = Debug|x64
{550C6AC3-EBE0-46CA-AE6C-EEEB59DDF35C}.Debug|x64.Build.0 = Debug|x64
{550C6AC3-EBE0-46CA-AE6C-EEEB59DDF35C}.Debug|x86.ActiveCfg = Debug|Win32
{550C6AC3-EBE0-46CA-AE6C-EEEB59DDF35C}.Debug|x86.Build.0 = Debug|Win32
{550C6AC3-EBE0-46CA-AE6C-EEEB59DDF35C}.Release|x64.ActiveCfg = Release|x64
{550C6AC3-EBE0-46CA-AE6C-EEEB59DDF35C}.Release|x64.Build.0 = Release|x64
{550C6AC3-EBE0-46CA-AE6C-EEEB59DDF35C}.Release|x86.ActiveCfg = Release|Win32
{550C6AC3-EBE0-46CA-AE6C-EEEB59DDF35C}.Release|x86.Build.0 = Release|Win32
{BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB}.Debug|x64.ActiveCfg = Debug|x64
{BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB}.Debug|x64.Build.0 = Debug|x64
{BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB}.Debug|x86.ActiveCfg = Debug|Win32
{BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB}.Debug|x86.Build.0 = Debug|Win32
{BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB}.Release|x64.ActiveCfg = Release|x64
{BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB}.Release|x64.Build.0 = Release|x64
{BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB}.Release|x86.ActiveCfg = Release|Win32
{BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB}.Release|x86.Build.0 = Release|Win32
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -61,5 +92,8 @@ Global
{4D27EDB9-5110-44FE-8CE2-D46C5AD3C55B} = {170B4A09-1B67-4A62-93AB-116EBCFF4A8C} {4D27EDB9-5110-44FE-8CE2-D46C5AD3C55B} = {170B4A09-1B67-4A62-93AB-116EBCFF4A8C}
{6ADBB60D-DBA0-411D-BD2D-A355EF8E0FE1} = {170B4A09-1B67-4A62-93AB-116EBCFF4A8C} {6ADBB60D-DBA0-411D-BD2D-A355EF8E0FE1} = {170B4A09-1B67-4A62-93AB-116EBCFF4A8C}
{91BA9E79-8242-4F7D-B997-0DFEC95EA22B} = {170B4A09-1B67-4A62-93AB-116EBCFF4A8C} {91BA9E79-8242-4F7D-B997-0DFEC95EA22B} = {170B4A09-1B67-4A62-93AB-116EBCFF4A8C}
{7DA88C6F-4470-495D-995A-4F633F3370C1} = {E0863FCC-8E72-490D-BE1B-458F12CA8298}
{550C6AC3-EBE0-46CA-AE6C-EEEB59DDF35C} = {E0863FCC-8E72-490D-BE1B-458F12CA8298}
{BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB} = {E0863FCC-8E72-490D-BE1B-458F12CA8298}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View file

@ -73,20 +73,45 @@ namespace fnd
// functions // functions
void addElement(const T& element) { mElements.push_back(element); } void addElement(const T& element) { mElements.push_back(element); }
size_t getIndexOf(const T& element) const size_t getIndexOf(const T& key) const
{ {
for (size_t i = 0; i < getSize(); i++) for (size_t i = 0; i < getSize(); i++)
{ {
if (getElement(i) == element) return i; if (getElement(i) == key) return i;
} }
throw Exception("LIST", "Element does not exist"); throw Exception("LIST", "Element does not exist");
} }
bool hasElement(const T& element) const bool hasElement(const T& key) const
{ {
try try
{ {
getIndexOf(element); getIndexOf(key);
} catch (const Exception&)
{
return false;
}
return true;
}
// special
template <class X>
size_t getIndexOf(const X& key) const
{
for (size_t i = 0; i < getSize(); i++)
{
if (getElement(i) == key) return i;
}
throw Exception("LIST", "Element does not exist");
}
template <class X>
bool hasElement(const X& key) const
{
try
{
getIndexOf(key);
} catch (const Exception&) } catch (const Exception&)
{ {
return false; return false;

View file

@ -23,12 +23,12 @@ void nx::AcidBinary::clear()
mEmbeddedPublicKey = crypto::rsa::sRsa2048Key(); mEmbeddedPublicKey = crypto::rsa::sRsa2048Key();
} }
const crypto::rsa::sRsa2048Key & nx::AcidBinary::getPublicKey() const const crypto::rsa::sRsa2048Key & nx::AcidBinary::getNcaHeader2RsaKey() const
{ {
return mEmbeddedPublicKey; return mEmbeddedPublicKey;
} }
void nx::AcidBinary::setPublicKey(const crypto::rsa::sRsa2048Key & key) void nx::AcidBinary::setNcaHeader2RsaKey(const crypto::rsa::sRsa2048Key & key)
{ {
mEmbeddedPublicKey = key; mEmbeddedPublicKey = key;
} }

View file

@ -32,8 +32,8 @@ namespace nx
// variables // variables
virtual void clear(); virtual void clear();
const crypto::rsa::sRsa2048Key& getPublicKey() const; const crypto::rsa::sRsa2048Key& getNcaHeader2RsaKey() const;
void setPublicKey(const crypto::rsa::sRsa2048Key& key); void setNcaHeader2RsaKey(const crypto::rsa::sRsa2048Key& key);
private: private:
const std::string kModuleName = "ACID_BINARY"; const std::string kModuleName = "ACID_BINARY";

View file

@ -46,6 +46,8 @@ namespace crypto
TitleKeyGenarateKey TitleKeyGenarateKey
}; };
u8 titlekey_generate_key[0x20] = { 39, 111, 56, 188, 68, 106, 241, 86, 31, 44, 90, 111, 116, 32, 93, 197, 25, 181, 59, 188, 178, 159, 211, 175, 212, 178, 162, 4, 28, 152, 117, 126 };
// aes128-cbc keys // aes128-cbc keys
u8 xci_header_key[16] = { 0x01, 0xc5, 0x8f, 0xe7, 0x2d, 0x13, 0x5a, 0xb2, 0x9a, 0x3f, 0x69, 0x33, 0x95, 0x74, 0xb1 }; u8 xci_header_key[16] = { 0x01, 0xc5, 0x8f, 0xe7, 0x2d, 0x13, 0x5a, 0xb2, 0x9a, 0x3f, 0x69, 0x33, 0x95, 0x74, 0xb1 };
u8 eticket_common_key[16] = { 0x55, 0xA3, 0xF8, 0x72, 0xBD, 0xC8, 0x0C, 0x55, 0x5A, 0x65, 0x43, 0x81, 0x13, 0x9E, 0x15, 0x3B }; // lol this 3ds dev common key u8 eticket_common_key[16] = { 0x55, 0xA3, 0xF8, 0x72, 0xBD, 0xC8, 0x0C, 0x55, 0x5A, 0x65, 0x43, 0x81, 0x13, 0x9E, 0x15, 0x3B }; // lol this 3ds dev common key
@ -69,7 +71,7 @@ namespace crypto
{ 0x1C, 0x9B, 0x7B, 0xFA, 0xF6, 0x28, 0x18, 0x3D, 0x71, 0xF6, 0x4D, 0x73, 0xF1, 0x50, 0xB9, 0xD2 } { 0x1C, 0x9B, 0x7B, 0xFA, 0xF6, 0x28, 0x18, 0x3D, 0x71, 0xF6, 0x4D, 0x73, 0xF1, 0x50, 0xB9, 0xD2 }
}; };
// aeskey, related to m_KeyAreaEncryptionKeyList (first in list?) // aeskey, related to m_KeyAreaEncryptionKeyList (first in list?)
u8 unk_aes_key[0x10] = { 0x3A, 0x7C, 0x3E, 0x38, 0x4A, 0x8F, 0x22, 0xFF, 0x4B, 0x21, 0x57, 0x19, 0xB7, 0x81, 0xAD, 0x0C }; u8 key_area_encryption_key_0[0x10] = { 0x3A, 0x7C, 0x3E, 0x38, 0x4A, 0x8F, 0x22, 0xFF, 0x4B, 0x21, 0x57, 0x19, 0xB7, 0x81, 0xAD, 0x0C };
} }
} }

View file

@ -10,10 +10,14 @@ void NcaHeader::exportBinary()
sNcaHeader* hdr = (sNcaHeader*)mBinaryBlob.getBytes(); sNcaHeader* hdr = (sNcaHeader*)mBinaryBlob.getBytes();
hdr->set_signature(kNcaSig.c_str()); hdr->set_signature(kNcaSig.c_str());
hdr->set_block_size(kDefaultBlockSize); hdr->set_distribution_type(mDistributionType);
hdr->set_content_type(mContentType);
hdr->set_key_generation(mEncryptionType);
hdr->set_key_area_encryption_key_index(mKeyIndex);
hdr->set_nca_size(mNcaSize); hdr->set_nca_size(mNcaSize);
hdr->set_program_id(mProgramId); hdr->set_program_id(mProgramId);
hdr->set_unk0(mUnk0); hdr->set_content_index(mContentIndex);
hdr->set_sdk_addon_version(mSdkAddonVersion);
// TODO: properly reconstruct NCA layout? atm in hands of user // TODO: properly reconstruct NCA layout? atm in hands of user
@ -24,13 +28,13 @@ void NcaHeader::exportBinary()
hdr->section(section).set_start(sizeToBlockNum(mSections[i].offset)); hdr->section(section).set_start(sizeToBlockNum(mSections[i].offset));
hdr->section(section).set_end(sizeToBlockNum(mSections[i].offset) + sizeToBlockNum(mSections[i].size)); hdr->section(section).set_end(sizeToBlockNum(mSections[i].offset) + sizeToBlockNum(mSections[i].size));
hdr->section(section).set_key_type(mSections[i].key_type); hdr->section(section).set_enabled(1);
hdr->section_hash(section) = mSections[i].hash; hdr->section_hash(section) = mSections[i].hash;
} }
for (size_t i = 0; i < kAesKeyNum; i++) for (size_t i = 0; i < kAesKeyNum; i++)
{ {
hdr->aes_key(i) = mAesKeys[i]; hdr->enc_aes_key(i) = mEncAesKeys[i];
} }
} }
@ -53,10 +57,14 @@ void NcaHeader::importBinary(const u8 * bytes, size_t len)
throw fnd::Exception(kModuleName, "NCA header corrupt"); throw fnd::Exception(kModuleName, "NCA header corrupt");
} }
mBlockSize = hdr->block_size(); mDistributionType = (DistributionType)hdr->distribution_type();
mContentType = (ContentType)hdr->content_type();
mEncryptionType = (EncryptionType)hdr->key_generation();
mKeyIndex = (EncryptionKeyIndex)hdr->key_area_encryption_key_index();
mNcaSize = hdr->nca_size(); mNcaSize = hdr->nca_size();
mProgramId = hdr->program_id(); mProgramId = hdr->program_id();
mUnk0 = hdr->unk0(); mContentIndex = hdr->content_index();
mSdkAddonVersion = hdr->sdk_addon_version();
for (size_t i = 0; i < kSectionNum; i++) for (size_t i = 0; i < kSectionNum; i++)
{ {
@ -66,24 +74,81 @@ void NcaHeader::importBinary(const u8 * bytes, size_t len)
// 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->section(section).start() == 0 && hdr->section(section).end() == 0) continue;
EncryptionType encType = mEncryptionType;
if (encType == CRYPT_AUTO)
{
if (mContentType == TYPE_PROGRAM && section == SECTION_LOGO)
{
encType = CRYPT_NONE;
}
else
{
encType = CRYPT_AESCTR;
}
}
// add high level struct // add high level struct
mSections.addElement({ blockNumToSize(hdr->section(section).start()), blockNumToSize(hdr->section(section).end() - hdr->section(section).start()), hdr->section(section).key_type(), hdr->section_hash(section) }); mSections.addElement({ blockNumToSize(hdr->section(section).start()), blockNumToSize(hdr->section(section).end() - hdr->section(section).start()), encType, hdr->section_hash(section) });
} }
for (size_t i = 0; i < kAesKeyNum; i++) for (size_t i = 0; i < kAesKeyNum; i++)
{ {
mAesKeys.addElement(hdr->aes_key(i)); mEncAesKeys.addElement(hdr->enc_aes_key(i));
} }
} }
void nx::NcaHeader::clear() void nx::NcaHeader::clear()
{ {
mBlockSize = 0; mDistributionType = DIST_DOWNLOAD;
mContentType = TYPE_PROGRAM;
mEncryptionType = CRYPT_AUTO;
mKeyIndex = KEY_DEFAULT;
mNcaSize = 0; mNcaSize = 0;
mProgramId = 0; mProgramId = 0;
mUnk0 = 0; mContentIndex = 0;
mSdkAddonVersion = 0;
mSections.clear(); mSections.clear();
mAesKeys.clear(); mEncAesKeys.clear();
}
nx::NcaHeader::DistributionType nx::NcaHeader::getDistributionType() const
{
return mDistributionType;
}
void nx::NcaHeader::setDistributionType(DistributionType type)
{
mDistributionType = type;
}
nx::NcaHeader::ContentType nx::NcaHeader::getContentType() const
{
return mContentType;
}
void nx::NcaHeader::setContentType(ContentType type)
{
mContentType = type;
}
nx::NcaHeader::EncryptionType nx::NcaHeader::getEncryptionType() const
{
return mEncryptionType;
}
void nx::NcaHeader::setEncryptionType(EncryptionType type)
{
mEncryptionType = type;
}
nx::NcaHeader::EncryptionKeyIndex nx::NcaHeader::getKeyIndex() const
{
return mKeyIndex;
}
void nx::NcaHeader::setKeyIndex(EncryptionKeyIndex index)
{
mKeyIndex = index;
} }
u64 NcaHeader::getNcaSize() const u64 NcaHeader::getNcaSize() const
@ -106,9 +171,24 @@ void NcaHeader::setProgramId(u64 program_id)
mProgramId = program_id; mProgramId = program_id;
} }
u32 NcaHeader::getUnk() const u32 nx::NcaHeader::getContentIndex() const
{ {
return mUnk0; return mContentIndex;
}
void nx::NcaHeader::setContentIndex(u32 index)
{
mContentIndex = index;
}
u32 nx::NcaHeader::getSdkAddonVersion() const
{
return mSdkAddonVersion;
}
void nx::NcaHeader::setSdkAddonVersion(u32 version)
{
mSdkAddonVersion = version;
} }
const fnd::List<NcaHeader::sSection>& NcaHeader::getSections() const const fnd::List<NcaHeader::sSection>& NcaHeader::getSections() const
@ -125,39 +205,43 @@ void NcaHeader::addSection(const sSection & section)
mSections.addElement(section); mSections.addElement(section);
} }
const fnd::List<crypto::aes::sAes128Key>& NcaHeader::getAesKeys() const const fnd::List<crypto::aes::sAes128Key>& NcaHeader::getEncAesKeys() const
{ {
return mAesKeys; return mEncAesKeys;
} }
void NcaHeader::addKey(const crypto::aes::sAes128Key & key) void NcaHeader::addEncAesKey(const crypto::aes::sAes128Key & key)
{ {
if (mAesKeys.getSize() >= kAesKeyNum) if (mEncAesKeys.getSize() >= kAesKeyNum)
{ {
throw fnd::Exception(kModuleName, "Too many NCA aes keys"); throw fnd::Exception(kModuleName, "Too many NCA aes keys");
} }
mAesKeys.addElement(key); mEncAesKeys.addElement(key);
} }
u64 NcaHeader::blockNumToSize(u32 block_num) const u64 NcaHeader::blockNumToSize(u32 block_num) const
{ {
return block_num*mBlockSize; return block_num*kBlockSize;
} }
u32 NcaHeader::sizeToBlockNum(u64 real_size) const u32 NcaHeader::sizeToBlockNum(u64 real_size) const
{ {
return align(real_size, mBlockSize)/mBlockSize; return align(real_size, kBlockSize)/kBlockSize;
} }
bool NcaHeader::isEqual(const NcaHeader & other) const bool NcaHeader::isEqual(const NcaHeader & other) const
{ {
return (mBlockSize == other.mBlockSize) \ return (mDistributionType == other.mDistributionType) \
&& (mContentType == other.mContentType) \
&& (mEncryptionType == other.mEncryptionType) \
&& (mKeyIndex == other.mKeyIndex) \
&& (mNcaSize == other.mNcaSize) \ && (mNcaSize == other.mNcaSize) \
&& (mProgramId == other.mProgramId) \ && (mProgramId == other.mProgramId) \
&& (mUnk0 == other.mUnk0) \ && (mContentIndex == other.mContentIndex) \
&& (mSdkAddonVersion == other.mSdkAddonVersion) \
&& (mSections == other.mSections) \ && (mSections == other.mSections) \
&& (mAesKeys == other.mAesKeys); && (mEncAesKeys == other.mEncAesKeys);
} }
void NcaHeader::copyFrom(const NcaHeader & other) void NcaHeader::copyFrom(const NcaHeader & other)
@ -168,13 +252,17 @@ void NcaHeader::copyFrom(const NcaHeader & other)
} }
else else
{ {
this->mBinaryBlob.clear(); mBinaryBlob.clear();
mBlockSize = other.mBlockSize; mDistributionType = other.mDistributionType;
mContentType = other.mContentType;
mEncryptionType = other.mEncryptionType;
mKeyIndex = other.mKeyIndex;
mNcaSize = other.mNcaSize; mNcaSize = other.mNcaSize;
mProgramId = other.mProgramId; mProgramId = other.mProgramId;
mUnk0 = other.mUnk0; mContentIndex = other.mContentIndex;
mSdkAddonVersion = other.mSdkAddonVersion;
mSections = other.mSections; mSections = other.mSections;
mAesKeys = other.mAesKeys; mEncAesKeys = other.mEncAesKeys;
} }
} }

View file

@ -12,18 +12,48 @@ namespace nx
class NcaHeader : public ISerialiseableBinary class NcaHeader : public ISerialiseableBinary
{ {
public: public:
enum DistributionType
{
DIST_DOWNLOAD,
DIST_GAME_CARD
};
enum ContentType
{
TYPE_PROGRAM,
TYPE_META,
TYPE_CONTROL,
TYPE_MANUAL,
TYPE_DATA,
};
enum EncryptionType
{
CRYPT_AUTO,
CRYPT_NONE,
CRYPT_AESCTR = 3
};
enum EncryptionKeyIndex
{
KEY_UNUSED_0,
KEY_UNUSED_1,
KEY_DEFAULT,
KEY_UNUSED_3,
};
struct sSection struct sSection
{ {
u64 offset; u64 offset;
u64 size; u64 size;
u8 key_type; EncryptionType enc_type;
crypto::sha::sSha256Hash hash; crypto::sha::sSha256Hash hash;
const sSection& operator=(const sSection& other) const sSection& operator=(const sSection& other)
{ {
offset = other.offset; offset = other.offset;
size = other.size; size = other.size;
key_type = other.key_type; enc_type = other.enc_type;
hash = other.hash; hash = other.hash;
return *this; return *this;
@ -33,7 +63,7 @@ namespace nx
{ {
return (offset == other.offset) \ return (offset == other.offset) \
&& (size == other.size) \ && (size == other.size) \
&& (key_type == other.key_type) \ && (enc_type == other.enc_type) \
&& (hash == other.hash); && (hash == other.hash);
} }
@ -43,7 +73,7 @@ namespace nx
} }
}; };
static const size_t kDefaultBlockSize = 0x200; static const size_t kBlockSize = 0x200;
NcaHeader(); NcaHeader();
NcaHeader(const NcaHeader& other); NcaHeader(const NcaHeader& other);
@ -63,21 +93,40 @@ namespace nx
// variables // variables
void clear(); void clear();
DistributionType getDistributionType() const;
void setDistributionType(DistributionType type);
ContentType getContentType() const;
void setContentType(ContentType type);
EncryptionType getEncryptionType() const;
void setEncryptionType(EncryptionType type);
EncryptionKeyIndex getKeyIndex() const;
void setKeyIndex(EncryptionKeyIndex index);
u64 getNcaSize() const; u64 getNcaSize() const;
void setNcaSize(u64 size); void setNcaSize(u64 size);
u64 getProgramId() const; u64 getProgramId() const;
void setProgramId(u64 program_id); void setProgramId(u64 program_id);
u32 getUnk() const; u32 getContentIndex() const;
void setContentIndex(u32 index);
u32 getSdkAddonVersion() const;
void setSdkAddonVersion(u32 version);
const fnd::List<sSection>& getSections() const; const fnd::List<sSection>& getSections() const;
void addSection(const sSection& section); void addSection(const sSection& section);
const fnd::List<crypto::aes::sAes128Key>& getAesKeys() const; const fnd::List<crypto::aes::sAes128Key>& getEncAesKeys() const;
void addKey(const crypto::aes::sAes128Key& key); void addEncAesKey(const crypto::aes::sAes128Key& key);
private: private:
const std::string kModuleName = "NCA_HEADER"; const std::string kModuleName = "NCA_HEADER";
const std::string kNcaSig = "NCA2"; const std::string kNcaSig = "NCA2";
static const size_t kSectionNum = 4; static const size_t kSectionNum = 4;
static const size_t kAesKeyNum = 4; static const size_t kAesKeyNum = 4;
static const u32 kDefaultSdkAddonVersion = 721920;
enum ProgramPartitionId
{
SECTION_CODE,
SECTION_DATA,
SECTION_LOGO,
};
#pragma pack (push, 1) #pragma pack (push, 1)
@ -85,19 +134,21 @@ namespace nx
{ {
private: private:
u8 signature_[4]; u8 signature_[4];
u8 reserved_0[2]; u8 distribution_type_;
u16 block_size_; u8 content_type_;
u8 key_generation_;
u8 key_area_encryption_key_index_;
u64 nca_size_; u64 nca_size_;
u64 program_id_; u64 program_id_;
u8 reserved_1[4]; u32 content_index_;
u32 unk_0_; u32 sdk_addon_version_;
u8 reserved_2[0x20]; u8 reserved_2[0x20];
struct sNcaSection struct sNcaSection
{ {
private: private:
u32 start_; // block units u32 start_; // block units
u32 end_; // block units u32 end_; // block units
u8 key_type_; u8 enabled_;
u8 reserved[7]; u8 reserved[7];
public: public:
u32 start() const { return le_word(start_); } u32 start() const { return le_word(start_); }
@ -106,17 +157,26 @@ namespace nx
u32 end() const { return le_word(end_); } u32 end() const { return le_word(end_); }
void set_end(u32 offset) { end_ = le_word(offset); } void set_end(u32 offset) { end_ = le_word(offset); }
u8 key_type() const { return key_type_; } u8 enabled() const { return enabled_; }
void set_key_type(u8 key_type) { key_type_ = key_type; } void set_enabled(u8 is_enabled) { enabled_ = is_enabled; }
} section_[kSectionNum]; } section_[kSectionNum];
crypto::sha::sSha256Hash section_hash_[kSectionNum]; crypto::sha::sSha256Hash section_hash_[kSectionNum];
crypto::aes::sAes128Key aes_keys_[kAesKeyNum]; crypto::aes::sAes128Key enc_aes_keys_[kAesKeyNum];
public: public:
const char* signature() const { return (const char*)signature_; } const char* signature() const { return (const char*)signature_; }
void set_signature(const char* signature) { memcpy(signature_, signature, 4); } void set_signature(const char* signature) { memcpy(signature_, signature, 4); }
u16 block_size() const { return le_hword(block_size_); } u8 distribution_type() const { return distribution_type_; }
void set_block_size(u16 block_size) { block_size_ = le_hword(block_size); } void set_distribution_type(u8 type) { distribution_type_ = type; }
u8 content_type() const { return content_type_; }
void set_content_type(u8 type) { content_type_ = type; }
u8 key_generation() const { return key_generation_; }
void set_key_generation(u8 type) { key_generation_ = type; }
u8 key_area_encryption_key_index() const { return key_area_encryption_key_index_; }
void set_key_area_encryption_key_index(u8 index) { key_area_encryption_key_index_ = index; }
u64 nca_size() const { return le_dword(nca_size_); } u64 nca_size() const { return le_dword(nca_size_); }
void set_nca_size(u64 nca_size) { nca_size_ = le_dword(nca_size); } void set_nca_size(u64 nca_size) { nca_size_ = le_dword(nca_size); }
@ -124,8 +184,11 @@ namespace nx
u64 program_id() const { return le_dword(program_id_); } u64 program_id() const { return le_dword(program_id_); }
void set_program_id(u64 program_id) { program_id_ = le_dword(program_id); } void set_program_id(u64 program_id) { program_id_ = le_dword(program_id); }
u32 unk0() const { return le_word(unk_0_); } u32 content_index() const { return le_word(content_index_); }
void set_unk0(u32 val) { unk_0_ = le_word(val); } void set_content_index(u32 index) { content_index_ = le_word(index); }
u32 sdk_addon_version() const { return le_word(sdk_addon_version_); }
void set_sdk_addon_version(u32 version) { sdk_addon_version_ = le_word(version); }
const sNcaSection& section(u8 index) const { return section_[index%kSectionNum]; } const sNcaSection& section(u8 index) const { return section_[index%kSectionNum]; }
sNcaSection& section(u8 index) { return section_[index%kSectionNum]; } sNcaSection& section(u8 index) { return section_[index%kSectionNum]; }
@ -133,8 +196,8 @@ namespace nx
const crypto::sha::sSha256Hash& section_hash(u8 index) const { return section_hash_[index%kSectionNum]; } const crypto::sha::sSha256Hash& section_hash(u8 index) const { return section_hash_[index%kSectionNum]; }
crypto::sha::sSha256Hash& section_hash(u8 index) { return section_hash_[index%kSectionNum]; } crypto::sha::sSha256Hash& section_hash(u8 index) { return section_hash_[index%kSectionNum]; }
const crypto::aes::sAes128Key& aes_key(u8 index) const { return aes_keys_[index%kAesKeyNum]; } const crypto::aes::sAes128Key& enc_aes_key(u8 index) const { return enc_aes_keys_[index%kAesKeyNum]; }
crypto::aes::sAes128Key& aes_key(u8 index) { return aes_keys_[index%kAesKeyNum]; } crypto::aes::sAes128Key& enc_aes_key(u8 index) { return enc_aes_keys_[index%kAesKeyNum]; }
}; };
#pragma pack (pop) #pragma pack (pop)
@ -142,12 +205,16 @@ namespace nx
fnd::MemoryBlob mBinaryBlob; fnd::MemoryBlob mBinaryBlob;
// data // data
u16 mBlockSize; DistributionType mDistributionType;
ContentType mContentType;
EncryptionType mEncryptionType;
EncryptionKeyIndex mKeyIndex;
u64 mNcaSize; u64 mNcaSize;
u64 mProgramId; u64 mProgramId;
u32 mUnk0; u32 mContentIndex;
u32 mSdkAddonVersion;
fnd::List<sSection> mSections; fnd::List<sSection> mSections;
fnd::List<crypto::aes::sAes128Key> mAesKeys; fnd::List<crypto::aes::sAes128Key> mEncAesKeys;
u64 blockNumToSize(u32 block_num) const; u64 blockNumToSize(u32 block_num) const;
u32 sizeToBlockNum(u64 real_size) const; u32 sizeToBlockNum(u64 real_size) const;

View file

@ -10,7 +10,6 @@ namespace nx
public nx::ISerialiseableBinary public nx::ISerialiseableBinary
{ {
public: public:
// move these enums to NpdmBinary?
enum InstructionType enum InstructionType
{ {
INSTR_32BIT, INSTR_32BIT,

170
lib/nx/PfsHeader.cpp Normal file
View file

@ -0,0 +1,170 @@
#include "PfsHeader.h"
nx::PfsHeader::PfsHeader()
{}
nx::PfsHeader::PfsHeader(const PfsHeader & other)
{
copyFrom(other);
}
nx::PfsHeader::PfsHeader(const u8 * bytes, size_t len)
{
importBinary(bytes, len);
}
void nx::PfsHeader::exportBinary()
{
// calculate name table size
size_t name_table_size = 0;
for (size_t i = 0; i < mFileList.getSize(); i++)
{
name_table_size += mFileList[i].name.length() + 1;
}
size_t pfs_header_size = align(sizeof(sPfsHeader) + sizeof(sPfsFile) * mFileList.getSize() + name_table_size, kPfsAlign);
// align name_table_size
name_table_size = pfs_header_size - (sizeof(sPfsHeader) + sizeof(sPfsFile) * mFileList.getSize());
// allocate pfs header binary
mBinaryBlob.alloc(pfs_header_size);
sPfsHeader* hdr = (sPfsHeader*)mBinaryBlob.getBytes();
// set header fields
hdr->set_signature(kPfsStructSig.c_str());
hdr->set_file_num(mFileList.getSize());
hdr->set_name_table_size(name_table_size);
// set file entries
sPfsFile* raw_files = (sPfsFile*)(mBinaryBlob.getBytes() + sizeof(sPfsHeader));
char* raw_name_table = (char*)(mBinaryBlob.getBytes() + sizeof(sPfsHeader) + sizeof(sPfsFile) * mFileList.getSize());
size_t raw_name_table_pos = 0;
calculateOffsets(pfs_header_size);
for (size_t i = 0; i < mFileList.getSize(); i++)
{
raw_files[i].set_offset(mFileList[i].offset - pfs_header_size);
raw_files[i].set_size(mFileList[i].size);
raw_files[i].set_name_offset(raw_name_table_pos);
strcpy(raw_name_table + raw_name_table_pos, mFileList[i].name.c_str());
raw_name_table_pos += mFileList[i].name.length() + 1;
}
}
void nx::PfsHeader::importBinary(const u8 * bytes, size_t len)
{
// check input length meets minimum size
if (len < sizeof(sPfsHeader))
{
throw fnd::Exception(kModuleName, "PFS header too small");
}
// import minimum header
mBinaryBlob.alloc(sizeof(sPfsHeader));
memcpy(mBinaryBlob.getBytes(), bytes, mBinaryBlob.getSize());
const sPfsHeader* hdr = (const sPfsHeader*)mBinaryBlob.getBytes();
// check struct signature
if (memcmp(hdr->signature(), kPfsStructSig.c_str(), 4) != 0)
{
throw fnd::Exception(kModuleName, "PFS header corrupt");
}
// determine complete header size
size_t pfs_full_header_size = sizeof(sPfsHeader) + sizeof(sPfsFile) * hdr->file_num() + hdr->name_table_size();
// check input length meets complete size
if (len < pfs_full_header_size)
{
throw fnd::Exception(kModuleName, "PFS header too small");
}
// import full header
mBinaryBlob.alloc(pfs_full_header_size);
memcpy(mBinaryBlob.getBytes(), bytes, mBinaryBlob.getSize());
hdr = (const sPfsHeader*)mBinaryBlob.getBytes();
// clear variables
clear();
// get pointers to raw data
const sPfsFile* raw_files = (const sPfsFile*)(mBinaryBlob.getBytes() + sizeof(sPfsHeader));
const char* raw_name_table = (const char*)(mBinaryBlob.getBytes() + sizeof(sPfsHeader) + sizeof(sPfsFile) * hdr->file_num());
// process file entries
for (size_t i = 0; i < hdr->file_num(); i++)
{
mFileList.addElement({ std::string(raw_name_table + raw_files[i].name_offset()), raw_files[i].offset() + pfs_full_header_size, raw_files[i].size() });
}
}
void nx::PfsHeader::clear()
{
mBinaryBlob.clear();
mFileList.clear();
}
const fnd::List<nx::PfsHeader::sFile>& nx::PfsHeader::getFileList() const
{
return mFileList;
}
void nx::PfsHeader::addFile(const std::string & name, size_t size)
{
mFileList.addElement({ name, 0, size });
}
void nx::PfsHeader::calculateOffsets(size_t data_offset)
{
for (size_t i = 0; i < mFileList.getSize(); i++)
{
mFileList[i].offset = (i == 0) ? data_offset : mFileList[i - 1].offset + mFileList[i - 1].size;
}
}
bool nx::PfsHeader::isEqual(const PfsHeader & other) const
{
return mFileList == other.mFileList;
}
void nx::PfsHeader::copyFrom(const PfsHeader & other)
{
if (other.getSize())
{
importBinary(other.getBytes(), other.getSize());
}
else
{
clear();
mFileList = other.mFileList;
}
}
bool nx::PfsHeader::operator==(const PfsHeader & other) const
{
return isEqual(other);
}
bool nx::PfsHeader::operator!=(const PfsHeader & other) const
{
return !isEqual(other);
}
void nx::PfsHeader::operator=(const PfsHeader & other)
{
copyFrom(other);
}
const u8 * nx::PfsHeader::getBytes() const
{
return mBinaryBlob.getBytes();
}
size_t nx::PfsHeader::getSize() const
{
return mBinaryBlob.getSize();
}

126
lib/nx/PfsHeader.h Normal file
View file

@ -0,0 +1,126 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/memory_blob.h>
#include <fnd/List.h>
#include <nx/ISerialiseableBinary.h>
namespace nx
{
class PfsHeader :
public ISerialiseableBinary
{
public:
struct sFile
{
std::string name;
size_t offset;
size_t size;
sFile& operator=(const sFile& other)
{
name = other.name;
offset = other.offset;
size = other.size;
return *this;
}
bool operator==(const sFile& other) const
{
return (name == other.name) \
&& (offset == other.offset) \
&& (size == other.size);
}
bool operator!=(const sFile& other) const
{
return !operator==(other);
}
bool operator==(const std::string& other) const
{
return (name == other);
}
bool operator!=(const std::string& other) const
{
return !operator==(other);
}
};
PfsHeader();
PfsHeader(const PfsHeader& other);
PfsHeader(const u8* bytes, size_t len);
bool operator==(const PfsHeader& other) const;
bool operator!=(const PfsHeader& other) const;
void operator=(const PfsHeader& other);
// to be used after export
const u8* getBytes() const;
size_t getSize() const;
// export/import binary
void exportBinary();
void importBinary(const u8* bytes, size_t len);
// variables
void clear();
const fnd::List<sFile>& getFileList() const;
void addFile(const std::string& name, size_t size);
private:
const std::string kModuleName = "PFS_HEADER";
const std::string kPfsStructSig = "PFS0";
static const size_t kPfsAlign = 0x40;
#pragma pack (push, 1)
struct sPfsFile
{
private:
u64 data_offset_;
u64 size_;
u64 name_offset_;
public:
u64 offset() const { return le_dword(data_offset_); }
void set_offset(u64 offset) { data_offset_ = le_dword(offset); }
u64 size() const { return le_dword(size_); }
void set_size(u64 size) { size_ = le_dword(size); }
u64 name_offset() const { return le_dword(name_offset_); }
void set_name_offset(u64 offset) { name_offset_ = le_dword(offset); }
};
struct sPfsHeader
{
private:
u8 signature_[4];
u32 file_num_;
u64 name_table_size_;
public:
const char* signature() const { return (const char*)signature_; }
void set_signature(const char* signature) { memcpy(signature_, signature, 4); }
u32 file_num() const { return le_word(file_num_); }
void set_file_num(u32 file_num) { file_num_ = le_word(file_num); }
u64 name_table_size() const { return le_dword(name_table_size_); }
void set_name_table_size(u64 size) { name_table_size_ = le_dword(size); }
};
#pragma pack (pop)
// binary blob
fnd::MemoryBlob mBinaryBlob;
// variables
fnd::List<sFile> mFileList;
void calculateOffsets(size_t data_offset);
bool isEqual(const PfsHeader& other) const;
void copyFrom(const PfsHeader& other);
};
}

6
lib/nx/XciHeader.cpp Normal file
View file

@ -0,0 +1,6 @@
#include "XciHeader.h"
nx::XciHeader::XciHeader()
{}

92
lib/nx/XciHeader.h Normal file
View file

@ -0,0 +1,92 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/memory_blob.h>
#include <fnd/List.h>
#include <nx/ISerialiseableBinary.h>
namespace nx
{
class XciHeader// :
//public ISerialiseableBinary
{
public:
XciHeader();
private:
#pragma pack (push, 1)
enum ContentMetaType
{
SYSTEM_PROGRAM = 1,
SYSTEM_DATA = 2,
SYSTEM_UPDATE = 3,
BOOT_IMAGE_PACKAGE = 4,
BOOT_IMAGE_PACKAGE_SAFE = 5,
APPLICATION = 128,
PATCH = 129,
ADD_ON_CONTENT = 130
};
enum ContentType
{
META,
PROGRAM,
DATA,
CONTROL,
HTML_DOCUMENT,
LEGAL_INFORMATION
};
enum ContentMetaAttribute
{
None = 0,
IncludesExFatDriver = 1
};
struct sContentMetaInfo
{
u64 id;
u32 version;
u8 type; // ContentMetaType
u8 attributes;
u8 reserved[2];
};
struct sContentInfo
{
u8 id[16];
u32 size_low;
u16 size_high;
u8 type;
u8 reserved;
};
struct sXciHeader
{
u8 signature[4];
u32 rom_area_start_page;
u32 backup_area_start_page;
u8 key_flag; // bit0-3 = KekIndex, bit4-7 = TitleKeyDecIndex
u8 rom_size; // this is an enum
u8 flags;
u8 package_id[8]; // stylised as 0x{0:x2}{1:x2}{2:x2}{3:x2}_{4:x2}{5:x2}{6:x2}{7:x2}
u32 valid_data_end_page;
u8 reserved_0[100];
u32 sel_sec;
u32 sel_t1_key;
u32 sel_key;
u32 lim_area;
u32 fw_version[2]; // [0]=minor, [1]=major
u32 acc_ctrl_1;
u8 reserved_1[0x10];
u32 fw_mode;
u32 cup_version;
u8 reserved_2[0x4];
u8 upp_hash[8]; // stylised as 0x{0:x2}{1:x2}{2:x2}{3:x2}_{4:x2}{5:x2}{6:x2}{7:x2}
u64 cup_id; // cup programID?
};
#pragma pack (pop)
};
}

View file

@ -44,12 +44,14 @@
<ClInclude Include="NpdmHeader.h" /> <ClInclude Include="NpdmHeader.h" />
<ClInclude Include="NcaHeader.h" /> <ClInclude Include="NcaHeader.h" />
<ClInclude Include="NXCrypto.h" /> <ClInclude Include="NXCrypto.h" />
<ClInclude Include="PfsHeader.h" />
<ClInclude Include="SacBinary.h" /> <ClInclude Include="SacBinary.h" />
<ClInclude Include="SacEntry.h" /> <ClInclude Include="SacEntry.h" />
<ClInclude Include="SystemCallEntry.h" /> <ClInclude Include="SystemCallEntry.h" />
<ClInclude Include="SystemCallHandler.h" /> <ClInclude Include="SystemCallHandler.h" />
<ClInclude Include="ThreadInfoEntry.h" /> <ClInclude Include="ThreadInfoEntry.h" />
<ClInclude Include="ThreadInfoHandler.h" /> <ClInclude Include="ThreadInfoHandler.h" />
<ClInclude Include="XciHeader.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="AciBinary.cpp" /> <ClCompile Include="AciBinary.cpp" />
@ -74,12 +76,14 @@
<ClCompile Include="NpdmBinary.cpp" /> <ClCompile Include="NpdmBinary.cpp" />
<ClCompile Include="NpdmHeader.cpp" /> <ClCompile Include="NpdmHeader.cpp" />
<ClCompile Include="NcaHeader.cpp" /> <ClCompile Include="NcaHeader.cpp" />
<ClCompile Include="PfsHeader.cpp" />
<ClCompile Include="SacBinary.cpp" /> <ClCompile Include="SacBinary.cpp" />
<ClCompile Include="SacEntry.cpp" /> <ClCompile Include="SacEntry.cpp" />
<ClCompile Include="SystemCallEntry.cpp" /> <ClCompile Include="SystemCallEntry.cpp" />
<ClCompile Include="SystemCallHandler.cpp" /> <ClCompile Include="SystemCallHandler.cpp" />
<ClCompile Include="ThreadInfoEntry.cpp" /> <ClCompile Include="ThreadInfoEntry.cpp" />
<ClCompile Include="ThreadInfoHandler.cpp" /> <ClCompile Include="ThreadInfoHandler.cpp" />
<ClCompile Include="XciHeader.cpp" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion> <VCProjectVersion>15.0</VCProjectVersion>

View file

@ -108,6 +108,12 @@
<ClInclude Include="NpdmBinary.h"> <ClInclude Include="NpdmBinary.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="PfsHeader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="XciHeader.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="NcaHeader.cpp"> <ClCompile Include="NcaHeader.cpp">
@ -194,5 +200,11 @@
<ClCompile Include="NpdmBinary.cpp"> <ClCompile Include="NpdmBinary.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="PfsHeader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="XciHeader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -1,4 +1,4 @@
PROGS = ncatool npdmtool PROGS = ncatool npdmtool pfstool
main: build main: build

View file

@ -5,8 +5,14 @@
#include <nx/NXCrypto.h> #include <nx/NXCrypto.h>
#include <nx/NcaHeader.h> #include <nx/NcaHeader.h>
#include <inttypes.h> #include <inttypes.h>
#ifdef _WIN32
#include <direct.h>
#else
#include <sys/stat.h>
#endif
const size_t kNcaSectorSize = nx::NcaHeader::kBlockSize;
const size_t kNcaSectorSize = nx::NcaHeader::kDefaultBlockSize;
void initNcaCtr(u8 ctr[crypto::aes::kAesBlockSize], u32 generation) void initNcaCtr(u8 ctr[crypto::aes::kAesBlockSize], u32 generation)
{ {
@ -25,6 +31,14 @@ void hexDump(const u8* data, size_t len)
} }
} }
void xorData(const u8* a, const u8* b, u8* out, size_t len)
{
for (size_t i = 0; i < len; i++)
{
out[i] = a[i] ^ b[i];
}
}
void decryptNcaSectorXts(const fnd::MemoryBlob& nca, u8 out[kNcaSectorSize], size_t sector, const u8* key1, const u8* key2) void decryptNcaSectorXts(const fnd::MemoryBlob& nca, u8 out[kNcaSectorSize], size_t sector, const u8* key1, const u8* key2)
{ {
u8 tweak[crypto::aes::kAesBlockSize]; u8 tweak[crypto::aes::kAesBlockSize];
@ -49,6 +63,63 @@ void dumpNcaSector(u8 out[kNcaSectorSize])
} }
} }
void dumpHxdStyleSector(u8* 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]);
}
*/
}
std::string kDistributionTypeStr[]
{
"Download",
"Game Card"
};
std::string kContentTypeStr[]
{
"Program",
"Meta",
"Control",
"Manual",
"Data"
};
std::string kEncryptionTypeStr[]
{
"Auto",
"None",
"UNKNOWN_2",
"AesCtr"
};
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
if (argc < 2) if (argc < 2)
@ -65,7 +136,7 @@ int main(int argc, char** argv)
u8 sector[kNcaSectorSize]; u8 sector[kNcaSectorSize];
// nca test // nca test
if (argc == 2) if (argc == 2 || argc == 3)
{ {
decryptNcaSectorXts(nca, sector, 1, crypto::aes::nx::nca_header_key[0], crypto::aes::nx::nca_header_key[1]); decryptNcaSectorXts(nca, sector, 1, crypto::aes::nx::nca_header_key[0], crypto::aes::nx::nca_header_key[1]);
@ -73,9 +144,14 @@ int main(int argc, char** argv)
hdr.importBinary(sector, kNcaSectorSize); hdr.importBinary(sector, kNcaSectorSize);
printf("[NCA Header]\n"); printf("[NCA Header]\n");
printf(" Size: 0x%" PRIx64 "\n", hdr.getNcaSize()); printf(" Dist. Type: %s\n", kDistributionTypeStr[hdr.getDistributionType()].c_str());
printf(" ProgID: 0x%016" PRIx64 "\n", hdr.getProgramId()); printf(" Type: %s\n", kContentTypeStr[hdr.getContentType()].c_str());
printf(" Unk0: 0x%" PRIx32 "\n", hdr.getUnk()); printf(" Enc. Type: %s\n", kEncryptionTypeStr[hdr.getEncryptionType()].c_str());
printf(" KeyIndex: %d\n", hdr.getKeyIndex());
printf(" Size: 0x%" PRIx64 "\n", hdr.getNcaSize());
printf(" ProgID: 0x%016" PRIx64 "\n", hdr.getProgramId());
printf(" Content. Idx: %" PRIu32 "\n", hdr.getContentIndex());
printf(" SdkAddon Ver.: v%" PRIu32 "\n", hdr.getSdkAddonVersion());
printf(" Sections:\n"); printf(" Sections:\n");
for (size_t i = 0; i < hdr.getSections().getSize(); i++) for (size_t i = 0; i < hdr.getSections().getSize(); i++)
{ {
@ -83,23 +159,41 @@ int main(int argc, char** argv)
printf(" %lu:\n", i); printf(" %lu:\n", i);
//printf(" Start Blk: %" PRId32 "\n", section.start_blk); //printf(" Start Blk: %" PRId32 "\n", section.start_blk);
//printf(" End Blk: %" PRId32 "\n", section.end_blk); //printf(" End Blk: %" PRId32 "\n", section.end_blk);
printf(" Offset: 0x%" PRIx64 "\n", section.offset); printf(" Offset: 0x%" PRIx64 "\n", section.offset);
printf(" Size: 0x%" PRIx64 "\n", section.size); printf(" Size: 0x%" PRIx64 "\n", section.size);
printf(" KeyType: 0x%02x\n", section.key_type); printf(" Enc. Type: %s\n", kEncryptionTypeStr[section.enc_type].c_str());
printf(" Hash: "); printf(" Hash: ");
hexDump(section.hash.bytes, crypto::sha::kSha256HashLen); hexDump(section.hash.bytes, crypto::sha::kSha256HashLen);
printf("\n"); printf("\n");
} }
printf(" AES Keys:\n"); printf(" Encrypted Body Keys:\n");
for (size_t i = 0; i < hdr.getAesKeys().getSize(); i++) for (size_t i = 0; i < hdr.getEncAesKeys().getSize(); i++)
{ {
printf(" %lu: ", i); printf(" %lu: ", i);
hexDump(hdr.getAesKeys()[i].key, crypto::aes::kAes128KeySize); hexDump(hdr.getEncAesKeys()[i].key, crypto::aes::kAes128KeySize);
printf("\n"); printf("\n");
} }
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
}
}
} }
} catch (const fnd::Exception& e) } catch (const fnd::Exception& e)
{ {
printf("%s\n",e.what()); printf("%s\n",e.what());

View file

@ -116,6 +116,9 @@
<ItemGroup> <ItemGroup>
<ClCompile Include="main.cpp" /> <ClCompile Include="main.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="makefile" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
</ImportGroup> </ImportGroup>

View file

@ -19,4 +19,7 @@
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="makefile" />
</ItemGroup>
</Project> </Project>

62
programs/pfstool/main.cpp Normal file
View file

@ -0,0 +1,62 @@
#include <cstdio>
#include <crypto/aes.h>
#include <fnd/io.h>
#include <fnd/memory_blob.h>
#include <nx/NXCrypto.h>
#include <nx/PfsHeader.h>
#include <inttypes.h>
#ifdef _WIN32
#include <direct.h>
#else
#include <sys/stat.h>
#endif
int main(int argc, char** argv)
{
if (argc < 2)
{
printf("usage: pfstool <file> [<output dir>]\n");
return 1;
}
try
{
fnd::MemoryBlob file;
fnd::io::readFile(argv[1], file);
// import
nx::PfsHeader pfs;
pfs.importBinary(file.getBytes(), file.getSize());
if (argc == 3)
{
#ifdef _WIN32
_mkdir(argv[2]);
#else
mkdir(argv[2], S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
#endif
}
printf("[PFS]\n");
for (size_t i = 0; i < pfs.getFileList().getSize(); i++)
{
printf(" %s (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", pfs.getFileList()[i].name.c_str(), pfs.getFileList()[i].offset, pfs.getFileList()[i].size);
if (argc == 3)
{
#ifdef _WIN32
fnd::io::writeFile(std::string(argv[2]) + "\\" + pfs.getFileList()[i].name, file.getBytes() + pfs.getFileList()[i].offset, pfs.getFileList()[i].size);
#else
fnd::io::writeFile(std::string(argv[2]) + "/" + pfs.getFileList()[i].name, file.getBytes() + pfs.getFileList()[i].offset, pfs.getFileList()[i].size);
#endif
}
}
} catch (const fnd::Exception& e)
{
printf("%s\n", e.what());
}
return 0;
}

39
programs/pfstool/makefile Normal file
View file

@ -0,0 +1,39 @@
# Sources
SRC_DIR = .
OBJS = $(foreach dir,$(SRC_DIR),$(subst .cpp,.o,$(wildcard $(dir)/*.cpp))) $(foreach dir,$(SRC_DIR),$(subst .c,.o,$(wildcard $(dir)/*.c)))
#local dependencies
DEPENDS = nx crypto fnd
LIB_DIR = ../../lib
LIBS = -L"$(LIB_DIR)" $(foreach dep,$(DEPENDS), -l"$(dep)")
INCS = -I"$(LIB_DIR)/"
OUTPUT = ../../bin/$(shell basename $(CURDIR))
# Compiler Settings
CXXFLAGS = -std=c++11 $(INCS) -D__STDC_FORMAT_MACROS -Wall -Wno-unused-but-set-variable -Wno-unused-value
ifeq ($(OS),Windows_NT)
# Windows Only Flags/Libs
CC = x86_64-w64-mingw32-gcc
CXX = x86_64-w64-mingw32-g++
CFLAGS +=
CXXFLAGS +=
LIBS += -static
else
# *nix Only Flags/Libs
CFLAGS +=
CXXFLAGS +=
LIBS +=
endif
all: build
rebuild: clean build
build: $(OBJS)
$(CXX) $(OBJS) $(LIBS) -o $(OUTPUT)
clean:
rm -rf $(OBJS) $(OUTPUT)

View file

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB}</ProjectGuid>
<RootNamespace>pfstool</RootNamespace>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\lib</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\lib</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\lib</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\lib</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="makefile" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="makefile" />
</ItemGroup>
</Project>