mirror of
https://github.com/jakcron/nstool.git
synced 2024-12-22 10:45:28 +00:00
final commit
This commit is contained in:
parent
98c933305d
commit
d730c1e3b8
34
NXTools.sln
34
NXTools.sln
|
@ -13,6 +13,21 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ncatool", "programs\ncatool
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{170B4A09-1B67-4A62-93AB-116EBCFF4A8C}"
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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|x86.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -61,5 +92,8 @@ Global
|
|||
{4D27EDB9-5110-44FE-8CE2-D46C5AD3C55B} = {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}
|
||||
{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
|
||||
EndGlobal
|
||||
|
|
|
@ -73,20 +73,45 @@ namespace fnd
|
|||
|
||||
// functions
|
||||
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++)
|
||||
{
|
||||
if (getElement(i) == element) return i;
|
||||
if (getElement(i) == key) return i;
|
||||
}
|
||||
|
||||
throw Exception("LIST", "Element does not exist");
|
||||
}
|
||||
bool hasElement(const T& element) const
|
||||
bool hasElement(const T& key) const
|
||||
{
|
||||
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&)
|
||||
{
|
||||
return false;
|
||||
|
|
|
@ -23,12 +23,12 @@ void nx::AcidBinary::clear()
|
|||
mEmbeddedPublicKey = crypto::rsa::sRsa2048Key();
|
||||
}
|
||||
|
||||
const crypto::rsa::sRsa2048Key & nx::AcidBinary::getPublicKey() const
|
||||
const crypto::rsa::sRsa2048Key & nx::AcidBinary::getNcaHeader2RsaKey() const
|
||||
{
|
||||
return mEmbeddedPublicKey;
|
||||
}
|
||||
|
||||
void nx::AcidBinary::setPublicKey(const crypto::rsa::sRsa2048Key & key)
|
||||
void nx::AcidBinary::setNcaHeader2RsaKey(const crypto::rsa::sRsa2048Key & key)
|
||||
{
|
||||
mEmbeddedPublicKey = key;
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ namespace nx
|
|||
// variables
|
||||
virtual void clear();
|
||||
|
||||
const crypto::rsa::sRsa2048Key& getPublicKey() const;
|
||||
void setPublicKey(const crypto::rsa::sRsa2048Key& key);
|
||||
const crypto::rsa::sRsa2048Key& getNcaHeader2RsaKey() const;
|
||||
void setNcaHeader2RsaKey(const crypto::rsa::sRsa2048Key& key);
|
||||
|
||||
private:
|
||||
const std::string kModuleName = "ACID_BINARY";
|
||||
|
|
|
@ -46,6 +46,8 @@ namespace crypto
|
|||
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
|
||||
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
|
||||
|
@ -69,7 +71,7 @@ namespace crypto
|
|||
{ 0x1C, 0x9B, 0x7B, 0xFA, 0xF6, 0x28, 0x18, 0x3D, 0x71, 0xF6, 0x4D, 0x73, 0xF1, 0x50, 0xB9, 0xD2 }
|
||||
};
|
||||
// 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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,10 +10,14 @@ void NcaHeader::exportBinary()
|
|||
sNcaHeader* hdr = (sNcaHeader*)mBinaryBlob.getBytes();
|
||||
|
||||
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_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
|
||||
|
||||
|
@ -24,13 +28,13 @@ void NcaHeader::exportBinary()
|
|||
|
||||
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_key_type(mSections[i].key_type);
|
||||
hdr->section(section).set_enabled(1);
|
||||
hdr->section_hash(section) = mSections[i].hash;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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();
|
||||
mProgramId = hdr->program_id();
|
||||
mUnk0 = hdr->unk0();
|
||||
mContentIndex = hdr->content_index();
|
||||
mSdkAddonVersion = hdr->sdk_addon_version();
|
||||
|
||||
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
|
||||
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
|
||||
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++)
|
||||
{
|
||||
mAesKeys.addElement(hdr->aes_key(i));
|
||||
mEncAesKeys.addElement(hdr->enc_aes_key(i));
|
||||
}
|
||||
}
|
||||
|
||||
void nx::NcaHeader::clear()
|
||||
{
|
||||
mBlockSize = 0;
|
||||
mDistributionType = DIST_DOWNLOAD;
|
||||
mContentType = TYPE_PROGRAM;
|
||||
mEncryptionType = CRYPT_AUTO;
|
||||
mKeyIndex = KEY_DEFAULT;
|
||||
mNcaSize = 0;
|
||||
mProgramId = 0;
|
||||
mUnk0 = 0;
|
||||
mContentIndex = 0;
|
||||
mSdkAddonVersion = 0;
|
||||
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
|
||||
|
@ -106,9 +171,24 @@ void NcaHeader::setProgramId(u64 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
|
||||
|
@ -125,39 +205,43 @@ void NcaHeader::addSection(const sSection & 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");
|
||||
}
|
||||
|
||||
mAesKeys.addElement(key);
|
||||
mEncAesKeys.addElement(key);
|
||||
}
|
||||
|
||||
u64 NcaHeader::blockNumToSize(u32 block_num) const
|
||||
{
|
||||
return block_num*mBlockSize;
|
||||
return block_num*kBlockSize;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return (mBlockSize == other.mBlockSize) \
|
||||
return (mDistributionType == other.mDistributionType) \
|
||||
&& (mContentType == other.mContentType) \
|
||||
&& (mEncryptionType == other.mEncryptionType) \
|
||||
&& (mKeyIndex == other.mKeyIndex) \
|
||||
&& (mNcaSize == other.mNcaSize) \
|
||||
&& (mProgramId == other.mProgramId) \
|
||||
&& (mUnk0 == other.mUnk0) \
|
||||
&& (mContentIndex == other.mContentIndex) \
|
||||
&& (mSdkAddonVersion == other.mSdkAddonVersion) \
|
||||
&& (mSections == other.mSections) \
|
||||
&& (mAesKeys == other.mAesKeys);
|
||||
&& (mEncAesKeys == other.mEncAesKeys);
|
||||
}
|
||||
|
||||
void NcaHeader::copyFrom(const NcaHeader & other)
|
||||
|
@ -168,13 +252,17 @@ void NcaHeader::copyFrom(const NcaHeader & other)
|
|||
}
|
||||
else
|
||||
{
|
||||
this->mBinaryBlob.clear();
|
||||
mBlockSize = other.mBlockSize;
|
||||
mBinaryBlob.clear();
|
||||
mDistributionType = other.mDistributionType;
|
||||
mContentType = other.mContentType;
|
||||
mEncryptionType = other.mEncryptionType;
|
||||
mKeyIndex = other.mKeyIndex;
|
||||
mNcaSize = other.mNcaSize;
|
||||
mProgramId = other.mProgramId;
|
||||
mUnk0 = other.mUnk0;
|
||||
mContentIndex = other.mContentIndex;
|
||||
mSdkAddonVersion = other.mSdkAddonVersion;
|
||||
mSections = other.mSections;
|
||||
mAesKeys = other.mAesKeys;
|
||||
mEncAesKeys = other.mEncAesKeys;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,18 +12,48 @@ namespace nx
|
|||
class NcaHeader : public ISerialiseableBinary
|
||||
{
|
||||
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
|
||||
{
|
||||
u64 offset;
|
||||
u64 size;
|
||||
u8 key_type;
|
||||
EncryptionType enc_type;
|
||||
crypto::sha::sSha256Hash hash;
|
||||
|
||||
const sSection& operator=(const sSection& other)
|
||||
{
|
||||
offset = other.offset;
|
||||
size = other.size;
|
||||
key_type = other.key_type;
|
||||
enc_type = other.enc_type;
|
||||
hash = other.hash;
|
||||
|
||||
return *this;
|
||||
|
@ -33,7 +63,7 @@ namespace nx
|
|||
{
|
||||
return (offset == other.offset) \
|
||||
&& (size == other.size) \
|
||||
&& (key_type == other.key_type) \
|
||||
&& (enc_type == other.enc_type) \
|
||||
&& (hash == other.hash);
|
||||
}
|
||||
|
||||
|
@ -43,7 +73,7 @@ namespace nx
|
|||
}
|
||||
};
|
||||
|
||||
static const size_t kDefaultBlockSize = 0x200;
|
||||
static const size_t kBlockSize = 0x200;
|
||||
|
||||
NcaHeader();
|
||||
NcaHeader(const NcaHeader& other);
|
||||
|
@ -63,21 +93,40 @@ namespace nx
|
|||
|
||||
// variables
|
||||
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;
|
||||
void setNcaSize(u64 size);
|
||||
u64 getProgramId() const;
|
||||
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;
|
||||
void addSection(const sSection& section);
|
||||
const fnd::List<crypto::aes::sAes128Key>& getAesKeys() const;
|
||||
void addKey(const crypto::aes::sAes128Key& key);
|
||||
const fnd::List<crypto::aes::sAes128Key>& getEncAesKeys() const;
|
||||
void addEncAesKey(const crypto::aes::sAes128Key& key);
|
||||
|
||||
private:
|
||||
const std::string kModuleName = "NCA_HEADER";
|
||||
const std::string kNcaSig = "NCA2";
|
||||
static const size_t kSectionNum = 4;
|
||||
static const size_t kAesKeyNum = 4;
|
||||
static const u32 kDefaultSdkAddonVersion = 721920;
|
||||
|
||||
enum ProgramPartitionId
|
||||
{
|
||||
SECTION_CODE,
|
||||
SECTION_DATA,
|
||||
SECTION_LOGO,
|
||||
};
|
||||
|
||||
#pragma pack (push, 1)
|
||||
|
||||
|
@ -85,19 +134,21 @@ namespace nx
|
|||
{
|
||||
private:
|
||||
u8 signature_[4];
|
||||
u8 reserved_0[2];
|
||||
u16 block_size_;
|
||||
u8 distribution_type_;
|
||||
u8 content_type_;
|
||||
u8 key_generation_;
|
||||
u8 key_area_encryption_key_index_;
|
||||
u64 nca_size_;
|
||||
u64 program_id_;
|
||||
u8 reserved_1[4];
|
||||
u32 unk_0_;
|
||||
u32 content_index_;
|
||||
u32 sdk_addon_version_;
|
||||
u8 reserved_2[0x20];
|
||||
struct sNcaSection
|
||||
{
|
||||
private:
|
||||
u32 start_; // block units
|
||||
u32 end_; // block units
|
||||
u8 key_type_;
|
||||
u8 enabled_;
|
||||
u8 reserved[7];
|
||||
public:
|
||||
u32 start() const { return le_word(start_); }
|
||||
|
@ -106,17 +157,26 @@ namespace nx
|
|||
u32 end() const { return le_word(end_); }
|
||||
void set_end(u32 offset) { end_ = le_word(offset); }
|
||||
|
||||
u8 key_type() const { return key_type_; }
|
||||
void set_key_type(u8 key_type) { key_type_ = key_type; }
|
||||
u8 enabled() const { return enabled_; }
|
||||
void set_enabled(u8 is_enabled) { enabled_ = is_enabled; }
|
||||
} section_[kSectionNum];
|
||||
crypto::sha::sSha256Hash section_hash_[kSectionNum];
|
||||
crypto::aes::sAes128Key aes_keys_[kAesKeyNum];
|
||||
crypto::aes::sAes128Key enc_aes_keys_[kAesKeyNum];
|
||||
public:
|
||||
const char* signature() const { return (const char*)signature_; }
|
||||
void set_signature(const char* signature) { memcpy(signature_, signature, 4); }
|
||||
|
||||
u16 block_size() const { return le_hword(block_size_); }
|
||||
void set_block_size(u16 block_size) { block_size_ = le_hword(block_size); }
|
||||
u8 distribution_type() const { return distribution_type_; }
|
||||
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_); }
|
||||
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_); }
|
||||
void set_program_id(u64 program_id) { program_id_ = le_dword(program_id); }
|
||||
|
||||
u32 unk0() const { return le_word(unk_0_); }
|
||||
void set_unk0(u32 val) { unk_0_ = le_word(val); }
|
||||
u32 content_index() const { return le_word(content_index_); }
|
||||
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]; }
|
||||
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]; }
|
||||
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]; }
|
||||
crypto::aes::sAes128Key& aes_key(u8 index) { return aes_keys_[index%kAesKeyNum]; }
|
||||
const crypto::aes::sAes128Key& enc_aes_key(u8 index) const { return enc_aes_keys_[index%kAesKeyNum]; }
|
||||
crypto::aes::sAes128Key& enc_aes_key(u8 index) { return enc_aes_keys_[index%kAesKeyNum]; }
|
||||
};
|
||||
#pragma pack (pop)
|
||||
|
||||
|
@ -142,12 +205,16 @@ namespace nx
|
|||
fnd::MemoryBlob mBinaryBlob;
|
||||
|
||||
// data
|
||||
u16 mBlockSize;
|
||||
DistributionType mDistributionType;
|
||||
ContentType mContentType;
|
||||
EncryptionType mEncryptionType;
|
||||
EncryptionKeyIndex mKeyIndex;
|
||||
u64 mNcaSize;
|
||||
u64 mProgramId;
|
||||
u32 mUnk0;
|
||||
u32 mContentIndex;
|
||||
u32 mSdkAddonVersion;
|
||||
fnd::List<sSection> mSections;
|
||||
fnd::List<crypto::aes::sAes128Key> mAesKeys;
|
||||
fnd::List<crypto::aes::sAes128Key> mEncAesKeys;
|
||||
|
||||
u64 blockNumToSize(u32 block_num) const;
|
||||
u32 sizeToBlockNum(u64 real_size) const;
|
||||
|
|
|
@ -10,7 +10,6 @@ namespace nx
|
|||
public nx::ISerialiseableBinary
|
||||
{
|
||||
public:
|
||||
// move these enums to NpdmBinary?
|
||||
enum InstructionType
|
||||
{
|
||||
INSTR_32BIT,
|
||||
|
|
170
lib/nx/PfsHeader.cpp
Normal file
170
lib/nx/PfsHeader.cpp
Normal 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
126
lib/nx/PfsHeader.h
Normal 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
6
lib/nx/XciHeader.cpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#include "XciHeader.h"
|
||||
|
||||
|
||||
|
||||
nx::XciHeader::XciHeader()
|
||||
{}
|
92
lib/nx/XciHeader.h
Normal file
92
lib/nx/XciHeader.h
Normal 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)
|
||||
};
|
||||
|
||||
}
|
|
@ -44,12 +44,14 @@
|
|||
<ClInclude Include="NpdmHeader.h" />
|
||||
<ClInclude Include="NcaHeader.h" />
|
||||
<ClInclude Include="NXCrypto.h" />
|
||||
<ClInclude Include="PfsHeader.h" />
|
||||
<ClInclude Include="SacBinary.h" />
|
||||
<ClInclude Include="SacEntry.h" />
|
||||
<ClInclude Include="SystemCallEntry.h" />
|
||||
<ClInclude Include="SystemCallHandler.h" />
|
||||
<ClInclude Include="ThreadInfoEntry.h" />
|
||||
<ClInclude Include="ThreadInfoHandler.h" />
|
||||
<ClInclude Include="XciHeader.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AciBinary.cpp" />
|
||||
|
@ -74,12 +76,14 @@
|
|||
<ClCompile Include="NpdmBinary.cpp" />
|
||||
<ClCompile Include="NpdmHeader.cpp" />
|
||||
<ClCompile Include="NcaHeader.cpp" />
|
||||
<ClCompile Include="PfsHeader.cpp" />
|
||||
<ClCompile Include="SacBinary.cpp" />
|
||||
<ClCompile Include="SacEntry.cpp" />
|
||||
<ClCompile Include="SystemCallEntry.cpp" />
|
||||
<ClCompile Include="SystemCallHandler.cpp" />
|
||||
<ClCompile Include="ThreadInfoEntry.cpp" />
|
||||
<ClCompile Include="ThreadInfoHandler.cpp" />
|
||||
<ClCompile Include="XciHeader.cpp" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
|
|
|
@ -108,6 +108,12 @@
|
|||
<ClInclude Include="NpdmBinary.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PfsHeader.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="XciHeader.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="NcaHeader.cpp">
|
||||
|
@ -194,5 +200,11 @@
|
|||
<ClCompile Include="NpdmBinary.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PfsHeader.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="XciHeader.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,4 +1,4 @@
|
|||
PROGS = ncatool npdmtool
|
||||
PROGS = ncatool npdmtool pfstool
|
||||
|
||||
main: build
|
||||
|
||||
|
|
|
@ -5,8 +5,14 @@
|
|||
#include <nx/NXCrypto.h>
|
||||
#include <nx/NcaHeader.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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (argc < 2)
|
||||
|
@ -65,7 +136,7 @@ int main(int argc, char** argv)
|
|||
u8 sector[kNcaSectorSize];
|
||||
|
||||
// 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]);
|
||||
|
||||
|
@ -73,9 +144,14 @@ int main(int argc, char** argv)
|
|||
hdr.importBinary(sector, kNcaSectorSize);
|
||||
|
||||
printf("[NCA Header]\n");
|
||||
printf(" Dist. Type: %s\n", kDistributionTypeStr[hdr.getDistributionType()].c_str());
|
||||
printf(" Type: %s\n", kContentTypeStr[hdr.getContentType()].c_str());
|
||||
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(" Unk0: 0x%" PRIx32 "\n", hdr.getUnk());
|
||||
printf(" Content. Idx: %" PRIu32 "\n", hdr.getContentIndex());
|
||||
printf(" SdkAddon Ver.: v%" PRIu32 "\n", hdr.getSdkAddonVersion());
|
||||
printf(" Sections:\n");
|
||||
for (size_t i = 0; i < hdr.getSections().getSize(); i++)
|
||||
{
|
||||
|
@ -85,21 +161,39 @@ int main(int argc, char** argv)
|
|||
//printf(" End Blk: %" PRId32 "\n", section.end_blk);
|
||||
printf(" Offset: 0x%" PRIx64 "\n", section.offset);
|
||||
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: ");
|
||||
hexDump(section.hash.bytes, crypto::sha::kSha256HashLen);
|
||||
printf("\n");
|
||||
}
|
||||
printf(" AES Keys:\n");
|
||||
for (size_t i = 0; i < hdr.getAesKeys().getSize(); i++)
|
||||
printf(" Encrypted Body Keys:\n");
|
||||
for (size_t i = 0; i < hdr.getEncAesKeys().getSize(); i++)
|
||||
{
|
||||
printf(" %lu: ", i);
|
||||
hexDump(hdr.getAesKeys()[i].key, crypto::aes::kAes128KeySize);
|
||||
hexDump(hdr.getEncAesKeys()[i].key, crypto::aes::kAes128KeySize);
|
||||
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)
|
||||
{
|
||||
printf("%s\n",e.what());
|
||||
|
|
|
@ -116,6 +116,9 @@
|
|||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="makefile" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
|
|
|
@ -19,4 +19,7 @@
|
|||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="makefile" />
|
||||
</ItemGroup>
|
||||
</Project>
|
62
programs/pfstool/main.cpp
Normal file
62
programs/pfstool/main.cpp
Normal 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
39
programs/pfstool/makefile
Normal 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)
|
125
programs/pfstool/pfstool.vcxproj
Normal file
125
programs/pfstool/pfstool.vcxproj
Normal 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>
|
25
programs/pfstool/pfstool.vcxproj.filters
Normal file
25
programs/pfstool/pfstool.vcxproj.filters
Normal 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>
|
Loading…
Reference in a new issue