mirror of
https://github.com/jakcron/nstool.git
synced 2024-12-22 18:55:29 +00:00
Merge pull request #10 from jakcron/nca-development
[nx|nstool] Nca development
This commit is contained in:
commit
c7d78d0f1e
|
@ -83,6 +83,7 @@ namespace nx
|
||||||
void setContentIndex(uint32_t index);
|
void setContentIndex(uint32_t index);
|
||||||
uint32_t getSdkAddonVersion() const;
|
uint32_t getSdkAddonVersion() const;
|
||||||
void setSdkAddonVersion(uint32_t version);
|
void setSdkAddonVersion(uint32_t version);
|
||||||
|
bool hasRightsId() const;
|
||||||
const byte_t* getRightsId() const;
|
const byte_t* getRightsId() const;
|
||||||
void setRightsId(const byte_t* rights_id);
|
void setRightsId(const byte_t* rights_id);
|
||||||
const fnd::List<sPartition>& getPartitions() const;
|
const fnd::List<sPartition>& getPartitions() const;
|
||||||
|
|
|
@ -9,5 +9,6 @@ namespace nx
|
||||||
static inline size_t sectorToOffset(size_t sector_index) { return sector_index * nx::nca::kSectorSize; }
|
static inline size_t sectorToOffset(size_t sector_index) { return sector_index * nx::nca::kSectorSize; }
|
||||||
static void decryptNcaHeader(const byte_t* src, byte_t* dst, const crypto::aes::sAesXts128Key& key);
|
static void decryptNcaHeader(const byte_t* src, byte_t* dst, const crypto::aes::sAesXts128Key& key);
|
||||||
static byte_t getMasterKeyRevisionFromKeyGeneration(byte_t key_generation);
|
static byte_t getMasterKeyRevisionFromKeyGeneration(byte_t key_generation);
|
||||||
|
static void getNcaPartitionAesCtr(const nx::sNcaFsHeader* hdr, byte_t* ctr);
|
||||||
};
|
};
|
||||||
}
|
}
|
27
lib/libnx/include/nx/hierarchicalsha256.h
Normal file
27
lib/libnx/include/nx/hierarchicalsha256.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <fnd/types.h>
|
||||||
|
#include <crypto/sha.h>
|
||||||
|
#include <fnd/ISerialiseableBinary.h>
|
||||||
|
|
||||||
|
namespace nx
|
||||||
|
{
|
||||||
|
namespace hierarchicalsha256
|
||||||
|
{
|
||||||
|
static const size_t kDefaultLevelNum = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma pack(push,1)
|
||||||
|
struct sHierarchicalSha256Header
|
||||||
|
{
|
||||||
|
crypto::sha::sSha256Hash master_hash;
|
||||||
|
le_uint32_t hash_block_size;
|
||||||
|
le_uint32_t hash_level_num;
|
||||||
|
struct sLayout
|
||||||
|
{
|
||||||
|
le_uint64_t offset;
|
||||||
|
le_uint64_t size;
|
||||||
|
} hash_data, hash_target;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ namespace nx
|
||||||
namespace ivfc
|
namespace ivfc
|
||||||
{
|
{
|
||||||
const std::string kIvfcSig = "IVFC";
|
const std::string kIvfcSig = "IVFC";
|
||||||
static const size_t kMaxIvfcLevel = 4;
|
static const size_t kMaxIvfcLevel = 7;
|
||||||
static const uint32_t kIvfcId = 0x20000;
|
static const uint32_t kIvfcId = 0x20000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,13 +24,13 @@ namespace nx
|
||||||
le_uint32_t level_num;
|
le_uint32_t level_num;
|
||||||
struct sIvfcLevelHeader
|
struct sIvfcLevelHeader
|
||||||
{
|
{
|
||||||
uint64_t logical_offset;
|
le_uint64_t logical_offset;
|
||||||
uint64_t hash_data_size;
|
le_uint64_t hash_data_size;
|
||||||
uint32_t block_size;
|
le_uint32_t block_size;
|
||||||
byte_t reserved[4];
|
byte_t reserved[4];
|
||||||
} level_header[ivfc::kMaxIvfcLevel];
|
} level_header[ivfc::kMaxIvfcLevel];
|
||||||
byte_t unk_0xA0[0x20];
|
byte_t reserved_00[0x8];
|
||||||
byte_t master_hash[0x20];
|
crypto::sha::sSha256Hash master_hash;
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@
|
||||||
#include <fnd/types.h>
|
#include <fnd/types.h>
|
||||||
#include <crypto/aes.h>
|
#include <crypto/aes.h>
|
||||||
#include <crypto/sha.h>
|
#include <crypto/sha.h>
|
||||||
|
#include <crypto/rsa.h>
|
||||||
#include <fnd/ISerialiseableBinary.h>
|
#include <fnd/ISerialiseableBinary.h>
|
||||||
#include <nx/ivfc.h>
|
#include <nx/ivfc.h>
|
||||||
|
#include <nx/hierarchicalsha256.h>
|
||||||
|
|
||||||
namespace nx
|
namespace nx
|
||||||
{
|
{
|
||||||
|
@ -19,6 +21,8 @@ namespace nx
|
||||||
static const size_t kAesKeyNum = 16;
|
static const size_t kAesKeyNum = 16;
|
||||||
static const size_t kRightsIdLen = 0x10;
|
static const size_t kRightsIdLen = 0x10;
|
||||||
static const size_t kKeyAreaEncryptionKeyNum = 3;
|
static const size_t kKeyAreaEncryptionKeyNum = 3;
|
||||||
|
static const size_t kFsHeaderHashSuperblockLen = 0x130;
|
||||||
|
static const uint16_t kDefaultFsHeaderVersion = 2;
|
||||||
|
|
||||||
enum ProgramPartitionId
|
enum ProgramPartitionId
|
||||||
{
|
{
|
||||||
|
@ -40,6 +44,7 @@ namespace nx
|
||||||
TYPE_CONTROL,
|
TYPE_CONTROL,
|
||||||
TYPE_MANUAL,
|
TYPE_MANUAL,
|
||||||
TYPE_DATA,
|
TYPE_DATA,
|
||||||
|
TYPE_PUBLIC_DATA
|
||||||
};
|
};
|
||||||
|
|
||||||
enum KeyBankIndex
|
enum KeyBankIndex
|
||||||
|
@ -67,7 +72,7 @@ namespace nx
|
||||||
enum HashType
|
enum HashType
|
||||||
{
|
{
|
||||||
HASH_AUTO,
|
HASH_AUTO,
|
||||||
HASH_UNK1,
|
HASH_NONE,
|
||||||
HASH_HIERARCHICAL_SHA256,
|
HASH_HIERARCHICAL_SHA256,
|
||||||
HASH_HIERARCHICAL_INTERGRITY // IVFC
|
HASH_HIERARCHICAL_INTERGRITY // IVFC
|
||||||
};
|
};
|
||||||
|
@ -78,7 +83,7 @@ namespace nx
|
||||||
CRYPT_NONE,
|
CRYPT_NONE,
|
||||||
CRYPT_AESXTS,
|
CRYPT_AESXTS,
|
||||||
CRYPT_AESCTR,
|
CRYPT_AESCTR,
|
||||||
CRYPT_BKTR
|
CRYPT_AESCTREX
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +93,7 @@ namespace nx
|
||||||
char signature[4];
|
char signature[4];
|
||||||
byte_t distribution_type;
|
byte_t distribution_type;
|
||||||
byte_t content_type;
|
byte_t content_type;
|
||||||
byte_t key_generation; // KeyGeneration
|
byte_t key_generation;
|
||||||
byte_t key_area_encryption_key_index;
|
byte_t key_area_encryption_key_index;
|
||||||
le_uint64_t content_size;
|
le_uint64_t content_size;
|
||||||
le_uint64_t program_id;
|
le_uint64_t program_id;
|
||||||
|
@ -110,23 +115,26 @@ namespace nx
|
||||||
|
|
||||||
struct sNcaFsHeader
|
struct sNcaFsHeader
|
||||||
{
|
{
|
||||||
le_uint16_t version; // usually 0x0002
|
le_uint16_t version;
|
||||||
byte_t format_type; // RomFs(0x00), PartitionFs(0x01)
|
byte_t format_type;
|
||||||
byte_t hash_type; // HashTypeAuto(0x00), HashTypeHierarchicalSha256(0x02), HashTypeHierarchicalIntegrity(0x03).RomFs uses (0x03) this is forced, PartitionFs uses (0x02).
|
byte_t hash_type;
|
||||||
byte_t encryption_type; // EncryptionTypeAuto(0x00), EncryptionTypeNone(0x01), EncryptionTypeAesCtr(0x03)
|
byte_t encryption_type;
|
||||||
byte_t reserved[3];
|
byte_t reserved_0[3];
|
||||||
|
union {
|
||||||
|
byte_t hash_superblock[nca::kFsHeaderHashSuperblockLen];
|
||||||
|
nx::sHierarchicalSha256Header hierarchicalsha256_header;
|
||||||
|
nx::sIvfcHeader ivfc_header;
|
||||||
|
};
|
||||||
|
crypto::aes::sAesIvCtr base_ctr;
|
||||||
|
byte_t reserved_1[0xB8];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sHierarchicalSha256Header
|
struct sNcaHeaderBlock
|
||||||
{
|
{
|
||||||
byte_t master_hash[0x20];
|
byte_t signature_main[crypto::rsa::kRsa2048Size];
|
||||||
le_uint32_t hash_block_size;
|
byte_t signature_acid[crypto::rsa::kRsa2048Size];
|
||||||
le_uint32_t unk_0x02;
|
sNcaHeader header;
|
||||||
struct sLayout
|
sNcaFsHeader fs_header[nx::nca::kPartitionNum];
|
||||||
{
|
|
||||||
le_uint64_t offset;
|
|
||||||
le_uint64_t size;
|
|
||||||
} hash_data, hash_target;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
|
@ -219,6 +219,19 @@ void nx::NcaHeader::setSdkAddonVersion(uint32_t version)
|
||||||
mSdkAddonVersion = version;
|
mSdkAddonVersion = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool nx::NcaHeader::hasRightsId() const
|
||||||
|
{
|
||||||
|
bool rightsIdIsSet = false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < nca::kRightsIdLen; i++)
|
||||||
|
{
|
||||||
|
if (mRightsId[i] != 0)
|
||||||
|
rightsIdIsSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rightsIdIsSet;
|
||||||
|
}
|
||||||
|
|
||||||
const byte_t* nx::NcaHeader::getRightsId() const
|
const byte_t* nx::NcaHeader::getRightsId() const
|
||||||
{
|
{
|
||||||
return mRightsId;
|
return mRightsId;
|
||||||
|
|
|
@ -47,3 +47,11 @@ byte_t nx::NcaUtils::getMasterKeyRevisionFromKeyGeneration(byte_t key_generation
|
||||||
|
|
||||||
return masterkey_rev;
|
return masterkey_rev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void nx::NcaUtils::getNcaPartitionAesCtr(const nx::sNcaFsHeader* hdr, byte_t* ctr)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
ctr[15-i] = hdr->base_ctr.iv[i];
|
||||||
|
}
|
||||||
|
}
|
71
programs/nstool/source/AesCtrWrappedIFile.cpp
Normal file
71
programs/nstool/source/AesCtrWrappedIFile.cpp
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#include "AesCtrWrappedIFile.h"
|
||||||
|
|
||||||
|
AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile& file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr) :
|
||||||
|
mFile(file),
|
||||||
|
mKey(key),
|
||||||
|
mBaseCtr(ctr)
|
||||||
|
{
|
||||||
|
mScratch.alloc(kAesCtrScratchAllocSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AesCtrWrappedIFile::size()
|
||||||
|
{
|
||||||
|
return mFile.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AesCtrWrappedIFile::seek(size_t offset)
|
||||||
|
{
|
||||||
|
mFile.seek(offset);
|
||||||
|
crypto::aes::AesIncrementCounter(mBaseCtr.iv, offset>>4, mCurrentCtr.iv);
|
||||||
|
mBlockOffset = offset & 0xf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AesCtrWrappedIFile::read(byte_t* out, size_t len)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < (len / kAesCtrScratchSize); i++)
|
||||||
|
{
|
||||||
|
mFile.read(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
|
||||||
|
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
|
||||||
|
memcpy(out + (i * kAesCtrScratchSize), mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len % kAesCtrScratchSize)
|
||||||
|
{
|
||||||
|
size_t read_len = len % kAesCtrScratchSize;
|
||||||
|
size_t read_pos = ((len / kAesCtrScratchSize) * kAesCtrScratchSize);
|
||||||
|
mFile.read(mScratch.getBytes() + mBlockOffset, read_len);
|
||||||
|
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
|
||||||
|
memcpy(out + read_pos, mScratch.getBytes() + mBlockOffset, read_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AesCtrWrappedIFile::read(byte_t* out, size_t offset, size_t len)
|
||||||
|
{
|
||||||
|
seek(offset);
|
||||||
|
read(out, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AesCtrWrappedIFile::write(const byte_t* out, size_t len)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < (len / kAesCtrScratchSize); i++)
|
||||||
|
{
|
||||||
|
memcpy(mScratch.getBytes() + mBlockOffset, out + (i * kAesCtrScratchSize), kAesCtrScratchSize);
|
||||||
|
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
|
||||||
|
mFile.write(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len % kAesCtrScratchSize)
|
||||||
|
{
|
||||||
|
size_t write_len = len % kAesCtrScratchSize;
|
||||||
|
size_t write_pos = ((len / kAesCtrScratchSize) * kAesCtrScratchSize);
|
||||||
|
memcpy(mScratch.getBytes() + mBlockOffset, out + write_pos, write_len);
|
||||||
|
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
|
||||||
|
mFile.write(mScratch.getBytes() + mBlockOffset, write_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AesCtrWrappedIFile::write(const byte_t* out, size_t offset, size_t len)
|
||||||
|
{
|
||||||
|
seek(offset);
|
||||||
|
write(out, len);
|
||||||
|
}
|
27
programs/nstool/source/AesCtrWrappedIFile.h
Normal file
27
programs/nstool/source/AesCtrWrappedIFile.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#include <fnd/IFile.h>
|
||||||
|
#include <fnd/MemoryBlob.h>
|
||||||
|
#include <crypto/aes.h>
|
||||||
|
|
||||||
|
class AesCtrWrappedIFile : public fnd::IFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AesCtrWrappedIFile(fnd::IFile& file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr);
|
||||||
|
|
||||||
|
size_t size();
|
||||||
|
void seek(size_t offset);
|
||||||
|
void read(byte_t* out, size_t len);
|
||||||
|
void read(byte_t* out, size_t offset, size_t len);
|
||||||
|
void write(const byte_t* out, size_t len);
|
||||||
|
void write(const byte_t* out, size_t offset, size_t len);
|
||||||
|
private:
|
||||||
|
const std::string kModuleName = "AesCtrWrappedIFile";
|
||||||
|
static const size_t kAesCtrScratchSize = 0x1000000;
|
||||||
|
static const size_t kAesCtrScratchAllocSize = kAesCtrScratchSize + crypto::aes::kAesBlockSize;
|
||||||
|
|
||||||
|
fnd::IFile& mFile;
|
||||||
|
crypto::aes::sAes128Key mKey;
|
||||||
|
crypto::aes::sAesIvCtr mBaseCtr, mCurrentCtr;
|
||||||
|
size_t mBlockOffset;
|
||||||
|
|
||||||
|
fnd::MemoryBlob mScratch;
|
||||||
|
};
|
0
programs/nstool/source/NcaPartitionProcess.cpp
Normal file
0
programs/nstool/source/NcaPartitionProcess.cpp
Normal file
16
programs/nstool/source/NcaPartitionProcess.h
Normal file
16
programs/nstool/source/NcaPartitionProcess.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <fnd/types.h>
|
||||||
|
#include <fnd/SimpleFile.h>
|
||||||
|
#include <nx/NcaHeader.h>
|
||||||
|
|
||||||
|
#include "nstool.h"
|
||||||
|
|
||||||
|
class NcaPartitionProcess
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NcaPartitionProcess();
|
||||||
|
private:
|
||||||
|
const std::string kModuleName = "NcaPartitionProcess";
|
||||||
|
|
||||||
|
};
|
476
programs/nstool/source/NcaProcess.cpp
Normal file
476
programs/nstool/source/NcaProcess.cpp
Normal file
|
@ -0,0 +1,476 @@
|
||||||
|
#include <fnd/SimpleTextOutput.h>
|
||||||
|
#include <nx/NcaUtils.h>
|
||||||
|
#include <nx/AesKeygen.h>
|
||||||
|
#include "NcaProcess.h"
|
||||||
|
#include "PfsProcess.h"
|
||||||
|
#include "RomfsProcess.h"
|
||||||
|
#include "OffsetAdjustedIFile.h"
|
||||||
|
#include "AesCtrWrappedIFile.h"
|
||||||
|
|
||||||
|
std::string kFormatVersionStr[]
|
||||||
|
{
|
||||||
|
"NCA2",
|
||||||
|
"NCA3"
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string kDistributionTypeStr[]
|
||||||
|
{
|
||||||
|
"Download",
|
||||||
|
"Game Card"
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string kContentTypeStr[]
|
||||||
|
{
|
||||||
|
"Program",
|
||||||
|
"Meta",
|
||||||
|
"Control",
|
||||||
|
"Manual",
|
||||||
|
"Data",
|
||||||
|
"PublicData"
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string kEncryptionTypeStr[]
|
||||||
|
{
|
||||||
|
"Auto",
|
||||||
|
"None",
|
||||||
|
"AesXts",
|
||||||
|
"AesCtr",
|
||||||
|
"AesCtrEx"
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string kHashTypeStr[]
|
||||||
|
{
|
||||||
|
"Auto",
|
||||||
|
"None",
|
||||||
|
"HierarchicalSha256",
|
||||||
|
"HierarchicalIntegrity"
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string kFormatTypeStr[]
|
||||||
|
{
|
||||||
|
"RomFs",
|
||||||
|
"PartitionFs"
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string kKaekIndexStr[]
|
||||||
|
{
|
||||||
|
"Application",
|
||||||
|
"Ocean",
|
||||||
|
"System"
|
||||||
|
};
|
||||||
|
|
||||||
|
void NcaProcess::displayHeader()
|
||||||
|
{
|
||||||
|
crypto::aes::sAes128Key zero_key;
|
||||||
|
memset(zero_key.key, 0, sizeof(zero_key));
|
||||||
|
|
||||||
|
printf("[NCA Header]\n");
|
||||||
|
printf(" Format Type: %s\n", kFormatVersionStr[mHdr.getFormatVersion()].c_str());
|
||||||
|
printf(" Dist. Type: %s\n", kDistributionTypeStr[mHdr.getDistributionType()].c_str());
|
||||||
|
printf(" Content Type: %s\n", kContentTypeStr[mHdr.getContentType()].c_str());
|
||||||
|
printf(" Key Generation: %d\n", mHdr.getKeyGeneration());
|
||||||
|
printf(" Kaek Index: %s (%d)\n", kKaekIndexStr[mHdr.getKaekIndex()].c_str(), mHdr.getKaekIndex());
|
||||||
|
printf(" Size: 0x%" PRIx64 "\n", mHdr.getContentSize());
|
||||||
|
printf(" ProgID: 0x%016" PRIx64 "\n", mHdr.getProgramId());
|
||||||
|
printf(" Content Index: %" PRIu32 "\n", mHdr.getContentIndex());
|
||||||
|
uint32_t ver = mHdr.getSdkAddonVersion();
|
||||||
|
printf(" SdkAddon Ver.: v%d.%d.%d (v%" PRIu32 ")\n", (ver>>24 & 0xff),(ver>>16 & 0xff),(ver>>8 & 0xff), ver);
|
||||||
|
printf(" RightsId: ");
|
||||||
|
fnd::SimpleTextOutput::hexDump(mHdr.getRightsId(), nx::nca::kRightsIdLen);
|
||||||
|
printf(" Key Area Keys: (Encrypted)\n");
|
||||||
|
for (size_t i = 0; i < mHdr.getEncAesKeys().getSize(); i++)
|
||||||
|
{
|
||||||
|
if (mHdr.getEncAesKeys()[i] != zero_key)
|
||||||
|
{
|
||||||
|
printf(" %2lu: ", i);
|
||||||
|
fnd::SimpleTextOutput::hexDump(mHdr.getEncAesKeys()[i].key, crypto::aes::kAes128KeySize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (mBodyKeyList.getSize() > 0)
|
||||||
|
{
|
||||||
|
printf(" Key Area Keys:\n");
|
||||||
|
for (size_t i = 0; i < mBodyKeyList.getSize(); i++)
|
||||||
|
{
|
||||||
|
printf(" %2lu: ", i);
|
||||||
|
fnd::SimpleTextOutput::hexDump(mBodyKeyList[i].key, crypto::aes::kAes128KeySize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
printf(" Partitions:\n");
|
||||||
|
for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++)
|
||||||
|
{
|
||||||
|
const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i];
|
||||||
|
nx::sNcaFsHeader& fs_header = mHdrBlock.fs_header[partition.index];
|
||||||
|
|
||||||
|
printf(" %lu:\n", i);
|
||||||
|
printf(" Index: %d\n", partition.index);
|
||||||
|
printf(" Offset: 0x%" PRIx64 "\n", partition.offset);
|
||||||
|
printf(" Size: 0x%" PRIx64 "\n", partition.size);
|
||||||
|
|
||||||
|
|
||||||
|
crypto::sha::sSha256Hash ncaFsHeaderHash;
|
||||||
|
crypto::sha::Sha256((byte_t*)&fs_header, sizeof(nx::sNcaFsHeader), ncaFsHeaderHash.bytes);
|
||||||
|
if (partition.hash.compare(ncaFsHeaderHash) == false)
|
||||||
|
{
|
||||||
|
throw fnd::Exception(kModuleName, "NcaFsHeader has bad sha256 hash");
|
||||||
|
}
|
||||||
|
|
||||||
|
//fnd::SimpleTextOutput::hxdStyleDump((byte_t*)&fs_header, sizeof(nx::sNcaFsHeader));
|
||||||
|
|
||||||
|
|
||||||
|
printf(" FsHeader:\n");
|
||||||
|
printf(" Version: 0x%d\n", fs_header.version.get());
|
||||||
|
printf(" Format Type: %s\n", kFormatTypeStr[fs_header.format_type].c_str());
|
||||||
|
printf(" Hash Type: %s\n", kHashTypeStr[fs_header.hash_type].c_str());
|
||||||
|
printf(" Enc. Type: %s\n", kEncryptionTypeStr[fs_header.encryption_type].c_str());
|
||||||
|
if (fs_header.encryption_type == nx::nca::CRYPT_AESCTR)
|
||||||
|
{
|
||||||
|
printf(" CTR: ");
|
||||||
|
crypto::aes::sAesIvCtr ctr;
|
||||||
|
nx::NcaUtils::getNcaPartitionAesCtr(&fs_header, ctr.iv);
|
||||||
|
crypto::aes::AesIncrementCounter(ctr.iv, partition.offset>>4, ctr.iv);
|
||||||
|
fnd::SimpleTextOutput::hexDump(ctr.iv, sizeof(crypto::aes::sAesIvCtr));
|
||||||
|
}
|
||||||
|
if (fs_header.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY)
|
||||||
|
{
|
||||||
|
printf(" HierarchicalIntegrity Header:\n");
|
||||||
|
printf(" Id: 0x%x\n", fs_header.ivfc_header.id.get());
|
||||||
|
printf(" MasterHashSize: 0x%x\n", fs_header.ivfc_header.master_hash_size.get());
|
||||||
|
printf(" LevelNum: %d\n", fs_header.ivfc_header.level_num.get());
|
||||||
|
for (size_t i = 0; i < fs_header.ivfc_header.level_num.get(); i++)
|
||||||
|
{
|
||||||
|
printf(" Level %d:\n", i);
|
||||||
|
printf(" LogicalOffset: 0x%" PRIx64 "\n", fs_header.ivfc_header.level_header[i].logical_offset.get());
|
||||||
|
printf(" HashDataSize: 0x%" PRIx64 "\n", fs_header.ivfc_header.level_header[i].hash_data_size.get());
|
||||||
|
printf(" BlockSize: 0x%" PRIx32 "\n", fs_header.ivfc_header.level_header[i].block_size.get());
|
||||||
|
}
|
||||||
|
printf(" Master Hash: ");
|
||||||
|
fnd::SimpleTextOutput::hexDump(fs_header.ivfc_header.master_hash.bytes, 0x20);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (fs_header.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256)
|
||||||
|
{
|
||||||
|
nx::sHierarchicalSha256Header& hash_hdr = fs_header.hierarchicalsha256_header;
|
||||||
|
printf(" HierarchicalSha256 Header:\n");
|
||||||
|
printf(" Master Hash: ");
|
||||||
|
fnd::SimpleTextOutput::hexDump(hash_hdr.master_hash.bytes, 0x20);
|
||||||
|
printf(" HashBlockSize: 0x%x\n", hash_hdr.hash_block_size.get());
|
||||||
|
printf(" HashLevelNum: 0x%x\n", hash_hdr.hash_level_num.get());
|
||||||
|
printf(" HashDataOffset: 0x%" PRIx64 "\n", hash_hdr.hash_data.offset.get());
|
||||||
|
printf(" HashDataSize: 0x%" PRIx64 "\n", hash_hdr.hash_data.size.get());
|
||||||
|
printf(" HashTargetOffset: 0x%" PRIx64 "\n", hash_hdr.hash_target.offset.get());
|
||||||
|
printf(" HashTargetSize: 0x%" PRIx64 "\n", hash_hdr.hash_target.size.get());
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf(" Hash Superblock:\n");
|
||||||
|
fnd::SimpleTextOutput::hxdStyleDump(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaProcess::generateNcaBodyEncryptionKeys()
|
||||||
|
{
|
||||||
|
// create zeros key
|
||||||
|
crypto::aes::sAes128Key zero_aesctr_key;
|
||||||
|
memset(zero_aesctr_key.key, 0, sizeof(zero_aesctr_key));
|
||||||
|
crypto::aes::sAesXts128Key zero_aesxts_key;
|
||||||
|
memset(zero_aesxts_key.key, 0, sizeof(zero_aesxts_key));
|
||||||
|
|
||||||
|
// get key data from header
|
||||||
|
byte_t masterkey_rev = nx::NcaUtils::getMasterKeyRevisionFromKeyGeneration(mHdr.getKeyGeneration());
|
||||||
|
byte_t keak_index = mHdr.getKaekIndex();
|
||||||
|
|
||||||
|
// set flag to indicate that the keys are not available
|
||||||
|
mBodyKeys.aes_ctr.isSet = false;
|
||||||
|
mBodyKeys.aes_xts.isSet = false;
|
||||||
|
|
||||||
|
// if this has a rights id, the key needs to be sourced from a ticket
|
||||||
|
if (mHdr.hasRightsId() == true)
|
||||||
|
{
|
||||||
|
// if the titlekey_kek is available
|
||||||
|
if (mKeyset->ticket.titlekey_kek[masterkey_rev] != zero_aesctr_key)
|
||||||
|
{
|
||||||
|
// the title key is provided (sourced from ticket)
|
||||||
|
if (mKeyset->nca.manual_title_key_aesctr != zero_aesctr_key)
|
||||||
|
{
|
||||||
|
nx::AesKeygen::generateKey(mBodyKeys.aes_ctr.var.key, mKeyset->nca.manual_title_key_aesctr.key, mKeyset->ticket.titlekey_kek[masterkey_rev].key);
|
||||||
|
mBodyKeys.aes_ctr.isSet = true;
|
||||||
|
}
|
||||||
|
if (mKeyset->nca.manual_title_key_aesxts != zero_aesxts_key)
|
||||||
|
{
|
||||||
|
nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[0], mKeyset->nca.manual_title_key_aesxts.key[0], mKeyset->ticket.titlekey_kek[masterkey_rev].key);
|
||||||
|
nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[1], mKeyset->nca.manual_title_key_aesxts.key[1], mKeyset->ticket.titlekey_kek[masterkey_rev].key);
|
||||||
|
mBodyKeys.aes_xts.isSet = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// otherwise decrypt key area
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if the titlekey_kek is available
|
||||||
|
if (mKeyset->nca.key_area_key[keak_index][masterkey_rev] != zero_aesctr_key)
|
||||||
|
{
|
||||||
|
nx::AesKeygen::generateKey(mBodyKeys.aes_ctr.var.key, mHdr.getEncAesKeys()[nx::nca::KEY_AESCTR].key, mKeyset->nca.key_area_key[keak_index][masterkey_rev].key);
|
||||||
|
mBodyKeys.aes_ctr.isSet = true;
|
||||||
|
|
||||||
|
nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[0], mHdr.getEncAesKeys()[nx::nca::KEY_AESXTS_0].key, mKeyset->nca.key_area_key[keak_index][masterkey_rev].key);
|
||||||
|
nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[1], mHdr.getEncAesKeys()[nx::nca::KEY_AESXTS_1].key, mKeyset->nca.key_area_key[keak_index][masterkey_rev].key);
|
||||||
|
mBodyKeys.aes_xts.isSet = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the keys weren't generated, check if the keys were supplied by the user
|
||||||
|
if (mBodyKeys.aes_ctr.isSet == false && mKeyset->nca.manual_body_key_aesctr != zero_aesctr_key)
|
||||||
|
{
|
||||||
|
mBodyKeys.aes_ctr = mKeyset->nca.manual_body_key_aesctr;
|
||||||
|
}
|
||||||
|
if (mBodyKeys.aes_xts.isSet == false && mKeyset->nca.manual_body_key_aesxts != zero_aesxts_key)
|
||||||
|
{
|
||||||
|
mBodyKeys.aes_xts = mKeyset->nca.manual_body_key_aesxts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaProcess::processPartitions()
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++)
|
||||||
|
{
|
||||||
|
const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i];
|
||||||
|
nx::sNcaFsHeader& fs_header = mHdrBlock.fs_header[partition.index];
|
||||||
|
|
||||||
|
crypto::aes::sAesIvCtr ctr;
|
||||||
|
nx::NcaUtils::getNcaPartitionAesCtr(&fs_header, ctr.iv);
|
||||||
|
|
||||||
|
// create reader
|
||||||
|
fnd::IFile* partitionReader = nullptr;
|
||||||
|
|
||||||
|
AesCtrWrappedIFile aesCtrFile = AesCtrWrappedIFile(*mReader, mBodyKeys.aes_ctr.var, ctr);
|
||||||
|
switch(fs_header.encryption_type)
|
||||||
|
{
|
||||||
|
case (nx::nca::CRYPT_AESXTS):
|
||||||
|
case (nx::nca::CRYPT_AESCTREX):
|
||||||
|
partitionReader = nullptr;
|
||||||
|
break;
|
||||||
|
case (nx::nca::CRYPT_AESCTR):
|
||||||
|
partitionReader = mBodyKeys.aes_ctr.isSet? &aesCtrFile : nullptr;
|
||||||
|
break;
|
||||||
|
case (nx::nca::CRYPT_NONE):
|
||||||
|
partitionReader = mReader;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the reader is null, skip
|
||||||
|
if (partitionReader == nullptr)
|
||||||
|
{
|
||||||
|
printf("[WARNING] NCA Partition %d not readable\n", partition.index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t data_offset = 0;
|
||||||
|
switch (fs_header.hash_type)
|
||||||
|
{
|
||||||
|
case (nx::nca::HASH_HIERARCHICAL_SHA256):
|
||||||
|
data_offset = fs_header.hierarchicalsha256_header.hash_target.offset.get();
|
||||||
|
break;
|
||||||
|
case (nx::nca::HASH_HIERARCHICAL_INTERGRITY):
|
||||||
|
data_offset = fs_header.ivfc_header.level_header[5].logical_offset.get();
|
||||||
|
break;
|
||||||
|
case (nx::nca::HASH_NONE):
|
||||||
|
data_offset = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw fnd::Exception(kModuleName, "Unknown hash type");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs_header.format_type == nx::nca::FORMAT_PFS0)
|
||||||
|
{
|
||||||
|
PfsProcess pfs;
|
||||||
|
pfs.setInputFile(*partitionReader);
|
||||||
|
pfs.setInputFileOffset(partition.offset + data_offset);
|
||||||
|
pfs.setCliOutputMode(mCliOutputType);
|
||||||
|
pfs.setListFs(mListFs);
|
||||||
|
if (mPartitionPath[partition.index].doExtract)
|
||||||
|
pfs.setExtractPath(mPartitionPath[partition.index].path);
|
||||||
|
pfs.process();
|
||||||
|
}
|
||||||
|
else if (fs_header.format_type == nx::nca::FORMAT_ROMFS)
|
||||||
|
{
|
||||||
|
RomfsProcess romfs;
|
||||||
|
romfs.setInputFile(*partitionReader);
|
||||||
|
romfs.setInputFileOffset(partition.offset + data_offset);
|
||||||
|
romfs.setCliOutputMode(mCliOutputType);
|
||||||
|
romfs.setListFs(mListFs);
|
||||||
|
if (mPartitionPath[partition.index].doExtract)
|
||||||
|
romfs.setExtractPath(mPartitionPath[partition.index].path);
|
||||||
|
romfs.process();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw fnd::Exception(kModuleName, "Unknown format type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NcaProcess::NcaProcess() :
|
||||||
|
mReader(nullptr),
|
||||||
|
mOffset(0),
|
||||||
|
mKeyset(nullptr),
|
||||||
|
mCliOutputType(OUTPUT_NORMAL),
|
||||||
|
mVerify(false),
|
||||||
|
mListFs(false)
|
||||||
|
{
|
||||||
|
mPartitionPath[0].doExtract = false;
|
||||||
|
mPartitionPath[1].doExtract = false;
|
||||||
|
mPartitionPath[2].doExtract = false;
|
||||||
|
mPartitionPath[3].doExtract = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NcaProcess::~NcaProcess()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaProcess::process()
|
||||||
|
{
|
||||||
|
fnd::MemoryBlob scratch;
|
||||||
|
|
||||||
|
if (mReader == nullptr)
|
||||||
|
{
|
||||||
|
throw fnd::Exception(kModuleName, "No file reader set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// read header block
|
||||||
|
mReader->read((byte_t*)&mHdrBlock, mOffset, sizeof(nx::sNcaHeaderBlock));
|
||||||
|
|
||||||
|
// decrypt header block
|
||||||
|
nx::NcaUtils::decryptNcaHeader((byte_t*)&mHdrBlock, (byte_t*)&mHdrBlock, mKeyset->nca.header_key);
|
||||||
|
|
||||||
|
// generate header hash
|
||||||
|
crypto::sha::Sha256((byte_t*)&mHdrBlock.header, sizeof(nx::sNcaHeader), mHdrHash.bytes);
|
||||||
|
|
||||||
|
// validate signature[0]
|
||||||
|
if (mVerify)
|
||||||
|
{
|
||||||
|
if (crypto::rsa::pss::rsaVerify(mKeyset->nca.header_sign_key, crypto::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_main) != 0)
|
||||||
|
{
|
||||||
|
// this is minimal even though it's a warning because it's a validation method
|
||||||
|
if (mCliOutputType >= OUTPUT_MINIMAL)
|
||||||
|
printf("[WARNING] NCA Header Main Signature: FAIL \n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// proccess main header
|
||||||
|
mHdr.importBinary((byte_t*)&mHdrBlock.header, sizeof(nx::sNcaHeader));
|
||||||
|
|
||||||
|
// validate fs headers
|
||||||
|
if (mVerify)
|
||||||
|
{
|
||||||
|
crypto::sha::sSha256Hash calc_hash;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++)
|
||||||
|
{
|
||||||
|
const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i];
|
||||||
|
|
||||||
|
crypto::sha::Sha256((const byte_t*)&mHdrBlock.fs_header[partition.index], sizeof(nx::sNcaFsHeader), calc_hash.bytes);
|
||||||
|
|
||||||
|
if (calc_hash.compare(partition.hash) == false)
|
||||||
|
{
|
||||||
|
// this is minimal even though it's a warning because it's a validation method
|
||||||
|
if (mCliOutputType >= OUTPUT_MINIMAL)
|
||||||
|
printf("[WARNING] NCA FsHeader[%d] Hash: FAIL \n", partition.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine keys
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
NCA is a file container
|
||||||
|
A hashed and signed file container
|
||||||
|
|
||||||
|
To verify a NCA: (R=regular step)
|
||||||
|
1 - decrypt header (R)
|
||||||
|
2 - verify signature[0]
|
||||||
|
3 - validate hashes of fs_headers
|
||||||
|
4 - determine how to read/decrypt the partitions (R)
|
||||||
|
5 - validate the partitions depending on their hash method
|
||||||
|
6 - if this NCA is a Program or Patch, open main.npdm from partition0
|
||||||
|
7 - validate ACID
|
||||||
|
8 - use public key in ACID to verify NCA signature[1]
|
||||||
|
|
||||||
|
Things to consider
|
||||||
|
* because of the manditory steps between verifcation steps
|
||||||
|
the NCA should be ready to be pulled to pieces before any printing is done
|
||||||
|
so the verification text can be presented without interuption
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// decrypt key area
|
||||||
|
generateNcaBodyEncryptionKeys();
|
||||||
|
|
||||||
|
if (mCliOutputType >= OUTPUT_NORMAL)
|
||||||
|
displayHeader();
|
||||||
|
|
||||||
|
processPartitions();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaProcess::setInputFile(fnd::IFile* reader)
|
||||||
|
{
|
||||||
|
mReader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaProcess::setInputFileOffset(size_t offset)
|
||||||
|
{
|
||||||
|
mOffset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaProcess::setKeyset(const sKeyset* keyset)
|
||||||
|
{
|
||||||
|
mKeyset = keyset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaProcess::setCliOutputMode(CliOutputType type)
|
||||||
|
{
|
||||||
|
mCliOutputType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaProcess::setVerifyMode(bool verify)
|
||||||
|
{
|
||||||
|
mVerify = verify;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaProcess::setPartition0ExtractPath(const std::string& path)
|
||||||
|
{
|
||||||
|
mPartitionPath[0].path = path;
|
||||||
|
mPartitionPath[0].doExtract = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaProcess::setPartition1ExtractPath(const std::string& path)
|
||||||
|
{
|
||||||
|
mPartitionPath[1].path = path;
|
||||||
|
mPartitionPath[1].doExtract = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaProcess::setPartition2ExtractPath(const std::string& path)
|
||||||
|
{
|
||||||
|
mPartitionPath[2].path = path;
|
||||||
|
mPartitionPath[2].doExtract = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaProcess::setPartition3ExtractPath(const std::string& path)
|
||||||
|
{
|
||||||
|
mPartitionPath[3].path = path;
|
||||||
|
mPartitionPath[3].doExtract = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NcaProcess::setListFs(bool list_fs)
|
||||||
|
{
|
||||||
|
mListFs = list_fs;
|
||||||
|
}
|
|
@ -2,7 +2,6 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <fnd/types.h>
|
#include <fnd/types.h>
|
||||||
#include <fnd/SimpleFile.h>
|
#include <fnd/SimpleFile.h>
|
||||||
#include <nx/nca.h>
|
|
||||||
#include <nx/NcaHeader.h>
|
#include <nx/NcaHeader.h>
|
||||||
|
|
||||||
#include "nstool.h"
|
#include "nstool.h"
|
||||||
|
@ -16,18 +15,54 @@ public:
|
||||||
void process();
|
void process();
|
||||||
|
|
||||||
// generic
|
// generic
|
||||||
void setInputFile(fnd::IFile& reader);
|
void setInputFile(fnd::IFile* reader);
|
||||||
void setInputFileOffset(size_t offset);
|
void setInputFileOffset(size_t offset);
|
||||||
void setKeyset(const sKeyset* keyset);
|
void setKeyset(const sKeyset* keyset);
|
||||||
void setCliOutputMode(CliOutputType type);
|
void setCliOutputMode(CliOutputType type);
|
||||||
void setVerifyMode(bool verify);
|
void setVerifyMode(bool verify);
|
||||||
|
|
||||||
// nca specfic
|
// nca specfic
|
||||||
|
void setPartition0ExtractPath(const std::string& path);
|
||||||
|
void setPartition1ExtractPath(const std::string& path);
|
||||||
|
void setPartition2ExtractPath(const std::string& path);
|
||||||
|
void setPartition3ExtractPath(const std::string& path);
|
||||||
|
void setListFs(bool list_fs);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const std::string kModuleName = "NcaProcess";
|
const std::string kModuleName = "NcaProcess";
|
||||||
|
|
||||||
byte_t mRawHeader[nx::nca::kHeaderSize];
|
// user options
|
||||||
std::string mPath;
|
fnd::IFile* mReader;
|
||||||
|
size_t mOffset;
|
||||||
const sKeyset* mKeyset;
|
const sKeyset* mKeyset;
|
||||||
|
CliOutputType mCliOutputType;
|
||||||
|
bool mVerify;
|
||||||
|
|
||||||
|
struct sExtract
|
||||||
|
{
|
||||||
|
std::string path;
|
||||||
|
bool doExtract;
|
||||||
|
} mPartitionPath[nx::nca::kPartitionNum];
|
||||||
|
|
||||||
|
bool mListFs;
|
||||||
|
|
||||||
|
// data
|
||||||
|
nx::sNcaHeaderBlock mHdrBlock;
|
||||||
|
crypto::sha::sSha256Hash mHdrHash;
|
||||||
|
nx::NcaHeader mHdr;
|
||||||
|
|
||||||
|
// crypto
|
||||||
|
struct sKeys
|
||||||
|
{
|
||||||
|
sOptional<crypto::aes::sAes128Key> aes_ctr;
|
||||||
|
sOptional<crypto::aes::sAesXts128Key> aes_xts;
|
||||||
|
} mBodyKeys;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void displayHeader();
|
||||||
|
|
||||||
|
void generateNcaBodyEncryptionKeys();
|
||||||
|
|
||||||
|
void processPartitions();
|
||||||
};
|
};
|
45
programs/nstool/source/OffsetAdjustedIFile.cpp
Normal file
45
programs/nstool/source/OffsetAdjustedIFile.cpp
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
#include "OffsetAdjustedIFile.h"
|
||||||
|
|
||||||
|
OffsetAdjustedIFile::OffsetAdjustedIFile(fnd::IFile& file, size_t offset, size_t size) :
|
||||||
|
mFile(file),
|
||||||
|
mBaseOffset(offset),
|
||||||
|
mCurrentOffset(0),
|
||||||
|
mSize(size)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t OffsetAdjustedIFile::size()
|
||||||
|
{
|
||||||
|
return mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffsetAdjustedIFile::seek(size_t offset)
|
||||||
|
{
|
||||||
|
mCurrentOffset = offset;
|
||||||
|
mFile.seek(offset + mBaseOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffsetAdjustedIFile::read(byte_t* out, size_t len)
|
||||||
|
{
|
||||||
|
seek(mCurrentOffset);
|
||||||
|
mFile.read(out, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffsetAdjustedIFile::read(byte_t* out, size_t offset, size_t len)
|
||||||
|
{
|
||||||
|
seek(offset);
|
||||||
|
read(out, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffsetAdjustedIFile::write(const byte_t* out, size_t len)
|
||||||
|
{
|
||||||
|
seek(mCurrentOffset);
|
||||||
|
mFile.write(out, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffsetAdjustedIFile::write(const byte_t* out, size_t offset, size_t len)
|
||||||
|
{
|
||||||
|
seek(offset);
|
||||||
|
write(out, len);
|
||||||
|
}
|
18
programs/nstool/source/OffsetAdjustedIFile.h
Normal file
18
programs/nstool/source/OffsetAdjustedIFile.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#include <fnd/IFile.h>
|
||||||
|
|
||||||
|
class OffsetAdjustedIFile : public fnd::IFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OffsetAdjustedIFile(fnd::IFile& file, size_t offset, size_t size);
|
||||||
|
|
||||||
|
size_t size();
|
||||||
|
void seek(size_t offset);
|
||||||
|
void read(byte_t* out, size_t len);
|
||||||
|
void read(byte_t* out, size_t offset, size_t len);
|
||||||
|
void write(const byte_t* out, size_t len);
|
||||||
|
void write(const byte_t* out, size_t offset, size_t len);
|
||||||
|
private:
|
||||||
|
fnd::IFile& mFile;
|
||||||
|
size_t mBaseOffset, mCurrentOffset;
|
||||||
|
size_t mSize;
|
||||||
|
};
|
|
@ -73,9 +73,13 @@ void PfsProcess::extractFs()
|
||||||
fnd::SimpleFile outFile;
|
fnd::SimpleFile outFile;
|
||||||
const fnd::List<nx::PfsHeader::sFile>& file = mPfs.getFileList();
|
const fnd::List<nx::PfsHeader::sFile>& file = mPfs.getFileList();
|
||||||
|
|
||||||
|
std::string file_path;
|
||||||
for (size_t i = 0; i < file.getSize(); i++)
|
for (size_t i = 0; i < file.getSize(); i++)
|
||||||
{
|
{
|
||||||
outFile.open(mExtractPath + kPathSeparator + file[i].name, outFile.Create);
|
file_path.clear();
|
||||||
|
fnd::io::appendToPath(file_path, mExtractPath);
|
||||||
|
fnd::io::appendToPath(file_path, file[i].name);
|
||||||
|
outFile.open(file_path, outFile.Create);
|
||||||
mReader->seek(mOffset + file[i].offset);
|
mReader->seek(mOffset + file[i].offset);
|
||||||
for (size_t j = 0; j < (file[i].size / kFileExportBlockSize); j++)
|
for (size_t j = 0; j < (file[i].size / kFileExportBlockSize); j++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -52,7 +52,6 @@ void UserSettings::showHelp()
|
||||||
printf(" nstool [--listfs] [--fsdir <dir>] <file>\n");
|
printf(" nstool [--listfs] [--fsdir <dir>] <file>\n");
|
||||||
printf(" --listfs Print file system\n");
|
printf(" --listfs Print file system\n");
|
||||||
printf(" --fsdir Extract file system to directory\n");
|
printf(" --fsdir Extract file system to directory\n");
|
||||||
/*
|
|
||||||
printf("\n NCA (Nintendo Content Archive)\n");
|
printf("\n NCA (Nintendo Content Archive)\n");
|
||||||
printf(" nstool [--listfs] [--bodykey <key> --titlekey <key>] [--part0 <dir> ...] <.nca file>\n");
|
printf(" nstool [--listfs] [--bodykey <key> --titlekey <key>] [--part0 <dir> ...] <.nca file>\n");
|
||||||
printf(" --listfs Print file system in embedded partitions\n");
|
printf(" --listfs Print file system in embedded partitions\n");
|
||||||
|
@ -62,7 +61,6 @@ void UserSettings::showHelp()
|
||||||
printf(" --part1 Extract \"partition 1\" to directory \n");
|
printf(" --part1 Extract \"partition 1\" to directory \n");
|
||||||
printf(" --part2 Extract \"partition 2\" to directory \n");
|
printf(" --part2 Extract \"partition 2\" to directory \n");
|
||||||
printf(" --part3 Extract \"partition 3\" to directory \n");
|
printf(" --part3 Extract \"partition 3\" to directory \n");
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string UserSettings::getInputPath() const
|
const std::string UserSettings::getInputPath() const
|
||||||
|
@ -115,6 +113,26 @@ const sOptional<std::string>& UserSettings::getFsPath() const
|
||||||
return mFsPath;
|
return mFsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sOptional<std::string>& UserSettings::getPart0Path() const
|
||||||
|
{
|
||||||
|
return mPart0Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sOptional<std::string>& UserSettings::getPart1Path() const
|
||||||
|
{
|
||||||
|
return mPart1Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sOptional<std::string>& UserSettings::getPart2Path() const
|
||||||
|
{
|
||||||
|
return mPart2Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sOptional<std::string>& UserSettings::getPart3Path() const
|
||||||
|
{
|
||||||
|
return mPart3Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void UserSettings::populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args)
|
void UserSettings::populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args)
|
||||||
{
|
{
|
||||||
|
@ -226,10 +244,28 @@ void UserSettings::populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args)
|
||||||
cmd_args.nca_bodykey = args[i+1];
|
cmd_args.nca_bodykey = args[i+1];
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (args[i] == "-o")
|
else if (args[i] == "--part0")
|
||||||
{
|
{
|
||||||
if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
|
if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
|
||||||
cmd_args.output_path = args[i+1];
|
cmd_args.part0_path = args[i+1];
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (args[i] == "--part1")
|
||||||
|
{
|
||||||
|
if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
|
||||||
|
cmd_args.part1_path = args[i+1];
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (args[i] == "--part2")
|
||||||
|
{
|
||||||
|
if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
|
||||||
|
cmd_args.part2_path = args[i+1];
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (args[i] == "--part3")
|
||||||
|
{
|
||||||
|
if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
|
||||||
|
cmd_args.part3_path = args[i+1];
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
|
@ -393,7 +429,14 @@ void UserSettings::populateKeyset(sCmdArgs& args)
|
||||||
|
|
||||||
if (args.nca_titlekey.isSet)
|
if (args.nca_titlekey.isSet)
|
||||||
{
|
{
|
||||||
decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key.key, sizeof(crypto::aes::sAes128Key));
|
if (args.nca_bodykey.var.length() == (sizeof(crypto::aes::sAes128Key)*2))
|
||||||
|
{
|
||||||
|
decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key_aesctr.key, sizeof(crypto::aes::sAes128Key));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key_aesxts.key[0], sizeof(crypto::aes::sAesXts128Key));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef _SAVE_KEYDATA
|
#undef _SAVE_KEYDATA
|
||||||
|
@ -475,6 +518,10 @@ void UserSettings::populateUserSettings(sCmdArgs& args)
|
||||||
mNormalPath = args.normal_path;
|
mNormalPath = args.normal_path;
|
||||||
mSecurePath = args.secure_path;
|
mSecurePath = args.secure_path;
|
||||||
mFsPath = args.fs_path;
|
mFsPath = args.fs_path;
|
||||||
|
mPart0Path = args.part0_path;
|
||||||
|
mPart1Path = args.part1_path;
|
||||||
|
mPart2Path = args.part2_path;
|
||||||
|
mPart3Path = args.part3_path;
|
||||||
|
|
||||||
// determine output path
|
// determine output path
|
||||||
if (args.verbose_output.isSet)
|
if (args.verbose_output.isSet)
|
||||||
|
|
|
@ -26,6 +26,10 @@ public:
|
||||||
const sOptional<std::string>& getNormalPath() const;
|
const sOptional<std::string>& getNormalPath() const;
|
||||||
const sOptional<std::string>& getSecurePath() const;
|
const sOptional<std::string>& getSecurePath() const;
|
||||||
const sOptional<std::string>& getFsPath() const;
|
const sOptional<std::string>& getFsPath() const;
|
||||||
|
const sOptional<std::string>& getPart0Path() const;
|
||||||
|
const sOptional<std::string>& getPart1Path() const;
|
||||||
|
const sOptional<std::string>& getPart2Path() const;
|
||||||
|
const sOptional<std::string>& getPart3Path() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const std::string kModuleName = "UserSettings";
|
const std::string kModuleName = "UserSettings";
|
||||||
|
@ -33,7 +37,6 @@ private:
|
||||||
struct sCmdArgs
|
struct sCmdArgs
|
||||||
{
|
{
|
||||||
sOptional<std::string> input_path;
|
sOptional<std::string> input_path;
|
||||||
sOptional<std::string> output_path;
|
|
||||||
sOptional<bool> devkit_keys;
|
sOptional<bool> devkit_keys;
|
||||||
sOptional<std::string> keyset_path;
|
sOptional<std::string> keyset_path;
|
||||||
sOptional<std::string> file_type;
|
sOptional<std::string> file_type;
|
||||||
|
@ -47,11 +50,14 @@ private:
|
||||||
sOptional<std::string> fs_path;
|
sOptional<std::string> fs_path;
|
||||||
sOptional<std::string> nca_titlekey;
|
sOptional<std::string> nca_titlekey;
|
||||||
sOptional<std::string> nca_bodykey;
|
sOptional<std::string> nca_bodykey;
|
||||||
|
sOptional<std::string> part0_path;
|
||||||
|
sOptional<std::string> part1_path;
|
||||||
|
sOptional<std::string> part2_path;
|
||||||
|
sOptional<std::string> part3_path;
|
||||||
|
|
||||||
void clear()
|
void clear()
|
||||||
{
|
{
|
||||||
input_path.isSet = false;
|
input_path.isSet = false;
|
||||||
output_path.isSet = false;
|
|
||||||
devkit_keys.isSet = false;
|
devkit_keys.isSet = false;
|
||||||
keyset_path.isSet = false;
|
keyset_path.isSet = false;
|
||||||
file_type.isSet = false;
|
file_type.isSet = false;
|
||||||
|
@ -65,6 +71,10 @@ private:
|
||||||
fs_path.isSet = false;
|
fs_path.isSet = false;
|
||||||
nca_titlekey.isSet = false;
|
nca_titlekey.isSet = false;
|
||||||
nca_bodykey.isSet = false;
|
nca_bodykey.isSet = false;
|
||||||
|
part0_path.isSet = false;
|
||||||
|
part1_path.isSet = false;
|
||||||
|
part2_path.isSet = false;
|
||||||
|
part3_path.isSet = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -80,6 +90,11 @@ private:
|
||||||
sOptional<std::string> mSecurePath;
|
sOptional<std::string> mSecurePath;
|
||||||
sOptional<std::string> mFsPath;
|
sOptional<std::string> mFsPath;
|
||||||
|
|
||||||
|
sOptional<std::string> mPart0Path;
|
||||||
|
sOptional<std::string> mPart1Path;
|
||||||
|
sOptional<std::string> mPart2Path;
|
||||||
|
sOptional<std::string> mPart3Path;
|
||||||
|
|
||||||
void populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args);
|
void populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args);
|
||||||
void populateKeyset(sCmdArgs& args);
|
void populateKeyset(sCmdArgs& args);
|
||||||
void populateUserSettings(sCmdArgs& args);
|
void populateUserSettings(sCmdArgs& args);
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#include "XciProcess.h"
|
#include "XciProcess.h"
|
||||||
#include "PfsProcess.h"
|
#include "PfsProcess.h"
|
||||||
#include "RomfsProcess.h"
|
#include "RomfsProcess.h"
|
||||||
//#include "NcaProcess.h"
|
#include "NcaProcess.h"
|
||||||
#include "NpdmProcess.h"
|
#include "NpdmProcess.h"
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,7 +54,6 @@ int main(int argc, char** argv)
|
||||||
}
|
}
|
||||||
else if (user_set.getFileType() == FILE_ROMFS)
|
else if (user_set.getFileType() == FILE_ROMFS)
|
||||||
{
|
{
|
||||||
|
|
||||||
RomfsProcess romfs;
|
RomfsProcess romfs;
|
||||||
|
|
||||||
romfs.setInputFile(inputFile);
|
romfs.setInputFile(inputFile);
|
||||||
|
@ -67,20 +66,28 @@ int main(int argc, char** argv)
|
||||||
romfs.setListFs(user_set.isListFs());
|
romfs.setListFs(user_set.isListFs());
|
||||||
|
|
||||||
romfs.process();
|
romfs.process();
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (user_set.getFileType() == FILE_NCA)
|
else if (user_set.getFileType() == FILE_NCA)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
NcaProcess nca;
|
NcaProcess nca;
|
||||||
|
|
||||||
nca.setNcaPath(user_set.getInputPath());
|
nca.setInputFile(&inputFile);
|
||||||
nca.setKeyset(user_set.getKeyset());
|
nca.setKeyset(&user_set.getKeyset());
|
||||||
nca.setCliOutputMode(user_set.getCliOutputType());
|
nca.setCliOutputMode(user_set.getCliOutputType());
|
||||||
nca.setVerifyMode(user_set.isVerifyFile());
|
nca.setVerifyMode(user_set.isVerifyFile());
|
||||||
|
|
||||||
|
|
||||||
|
if (user_set.getPart0Path().isSet)
|
||||||
|
nca.setPartition0ExtractPath(user_set.getPart0Path().var);
|
||||||
|
if (user_set.getPart1Path().isSet)
|
||||||
|
nca.setPartition1ExtractPath(user_set.getPart1Path().var);
|
||||||
|
if (user_set.getPart2Path().isSet)
|
||||||
|
nca.setPartition2ExtractPath(user_set.getPart2Path().var);
|
||||||
|
if (user_set.getPart3Path().isSet)
|
||||||
|
nca.setPartition3ExtractPath(user_set.getPart3Path().var);
|
||||||
|
nca.setListFs(user_set.isListFs());
|
||||||
|
|
||||||
nca.process();
|
nca.process();
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
else if (user_set.getFileType() == FILE_NPDM)
|
else if (user_set.getFileType() == FILE_NPDM)
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,11 +9,6 @@
|
||||||
static const size_t kMasterKeyNum = 0x20;
|
static const size_t kMasterKeyNum = 0x20;
|
||||||
static const size_t kNcaKeakNum = nx::nca::kKeyAreaEncryptionKeyNum;
|
static const size_t kNcaKeakNum = nx::nca::kKeyAreaEncryptionKeyNum;
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
const std::string kPathSeparator = "\\";
|
|
||||||
#else
|
|
||||||
const std::string kPathSeparator = "/";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum FileType
|
enum FileType
|
||||||
{
|
{
|
||||||
|
@ -63,7 +58,8 @@ struct sKeyset
|
||||||
crypto::aes::sAesXts128Key header_key;
|
crypto::aes::sAesXts128Key header_key;
|
||||||
crypto::aes::sAes128Key key_area_key[kNcaKeakNum][kMasterKeyNum];
|
crypto::aes::sAes128Key key_area_key[kNcaKeakNum][kMasterKeyNum];
|
||||||
|
|
||||||
crypto::aes::sAes128Key manual_title_key;
|
crypto::aes::sAes128Key manual_title_key_aesctr;
|
||||||
|
crypto::aes::sAesXts128Key manual_title_key_aesxts;
|
||||||
crypto::aes::sAes128Key manual_body_key_aesctr;
|
crypto::aes::sAes128Key manual_body_key_aesctr;
|
||||||
crypto::aes::sAesXts128Key manual_body_key_aesxts;
|
crypto::aes::sAesXts128Key manual_body_key_aesxts;
|
||||||
} nca;
|
} nca;
|
||||||
|
|
Loading…
Reference in a new issue